generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } enum Role { SUPER_ADMIN TENANT_ADMIN OPERATOR VIEWER } enum TenantStatus { ACTIVE SUSPENDED TRIAL CANCELLED } enum VmStatus { RUNNING STOPPED PAUSED MIGRATING ERROR } enum VmType { QEMU LXC } enum NodeStatus { ONLINE OFFLINE MAINTENANCE } enum Currency { NGN USD GHS KES ZAR } enum PaymentProvider { PAYSTACK FLUTTERWAVE MANUAL } enum InvoiceStatus { DRAFT PENDING PAID OVERDUE CANCELLED REFUNDED } enum BackupStatus { PENDING RUNNING COMPLETED FAILED EXPIRED } enum BackupType { FULL INCREMENTAL SNAPSHOT } enum BackupSchedule { MANUAL DAILY WEEKLY MONTHLY } enum BackupSource { LOCAL PBS REMOTE } enum BackupRestoreMode { FULL_VM FILES SINGLE_FILE } enum BackupRestoreStatus { PENDING RUNNING COMPLETED FAILED } enum SnapshotFrequency { HOURLY DAILY WEEKLY } enum Severity { INFO WARNING ERROR CRITICAL } enum SecurityStatus { OPEN INVESTIGATING RESOLVED FALSE_POSITIVE } enum Direction { INBOUND OUTBOUND BOTH } enum FirewallAction { ALLOW DENY RATE_LIMIT LOG } enum Protocol { TCP UDP ICMP ANY } enum AppliesTo { ALL_NODES ALL_VMS SPECIFIC_NODE SPECIFIC_VM } enum ResourceType { VM TENANT USER BACKUP INVOICE NODE NETWORK SYSTEM SECURITY BILLING } enum SettingType { PROXMOX PAYMENT EMAIL SECURITY NETWORK GENERAL } enum OperationTaskStatus { QUEUED RUNNING SUCCESS FAILED RETRYING CANCELED } enum OperationTaskType { VM_POWER VM_MIGRATION VM_REINSTALL VM_NETWORK VM_CONFIG VM_BACKUP VM_SNAPSHOT VM_CREATE VM_DELETE SYSTEM_SYNC } enum PowerScheduleAction { START STOP RESTART SHUTDOWN } enum TemplateType { APPLICATION KVM_TEMPLATE LXC_TEMPLATE ISO_IMAGE ARCHIVE } enum ProductType { VPS CLOUD } enum ServiceLifecycleStatus { ACTIVE SUSPENDED TERMINATED } enum IpVersion { IPV4 IPV6 } enum IpScope { PUBLIC PRIVATE } enum IpAddressStatus { AVAILABLE ASSIGNED RESERVED RETIRED } enum IpAssignmentType { PRIMARY ADDITIONAL FLOATING } enum PrivateNetworkType { BRIDGE VLAN SDN_ZONE VNET } enum PrivateNetworkAttachmentStatus { ATTACHED DETACHED } enum IpAllocationStrategy { FIRST_AVAILABLE BEST_FIT } enum HealthCheckTargetType { NODE VM CLUSTER } enum HealthCheckType { CONNECTIVITY RESOURCE_THRESHOLD SERVICE_PORT } enum HealthCheckStatus { PASS WARNING FAIL } enum MonitoringAlertStatus { OPEN ACKNOWLEDGED RESOLVED } enum AlertChannel { EMAIL WEBHOOK IN_APP } enum AlertDispatchStatus { QUEUED SENT FAILED SKIPPED } model User { id String @id @default(cuid()) email String @unique password_hash String full_name String? avatar_url String? profile_metadata Json @default("{}") role Role @default(VIEWER) tenant_id String? is_active Boolean @default(true) must_change_password Boolean @default(false) mfa_enabled Boolean @default(false) mfa_secret String? mfa_recovery_codes Json @default("[]") password_changed_at DateTime? last_login_at DateTime? created_at DateTime @default(now()) updated_at DateTime @updatedAt tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: SetNull) auth_sessions AuthSession[] password_reset_tokens PasswordResetToken[] @@index([tenant_id]) } model Tenant { id String @id @default(cuid()) name String slug String @unique status TenantStatus @default(ACTIVE) trial_starts_at DateTime? trial_ends_at DateTime? trial_grace_ends_at DateTime? trial_days Int? trial_locked Boolean @default(false) plan String @default("starter") owner_email String member_emails Json @default("[]") vm_limit Int @default(5) cpu_limit Int @default(16) ram_limit_mb Int @default(32768) disk_limit_gb Int @default(500) balance Decimal @default(0) @db.Decimal(14, 2) currency Currency @default(NGN) payment_provider PaymentProvider @default(PAYSTACK) notes String? created_at DateTime @default(now()) updated_at DateTime @updatedAt users User[] virtual_machines VirtualMachine[] invoices Invoice[] usage_records UsageRecord[] provisioned_services ProvisionedService[] backups Backup[] backup_policies BackupPolicy[] assigned_ip_addresses IpAddressPool[] @relation("AssignedIpTenant") ip_assignments IpAssignment[] private_network_attachments PrivateNetworkAttachment[] ip_quota TenantIpQuota? reserved_ip_ranges IpReservedRange[] ip_pool_policies IpPoolPolicy[] health_checks ServerHealthCheck[] monitoring_alert_rules MonitoringAlertRule[] monitoring_alert_events MonitoringAlertEvent[] @@index([status]) @@index([trial_ends_at]) @@index([owner_email]) } model AuthSession { id String @id @default(cuid()) user_id String refresh_token_hash String @unique ip_address String? user_agent String? issued_at DateTime @default(now()) expires_at DateTime last_used_at DateTime? revoked_at DateTime? created_at DateTime @default(now()) updated_at DateTime @updatedAt user User @relation(fields: [user_id], references: [id], onDelete: Cascade) @@index([user_id, revoked_at]) @@index([expires_at]) } model PasswordResetToken { id String @id @default(cuid()) user_id String token_hash String @unique expires_at DateTime used_at DateTime? created_at DateTime @default(now()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade) @@index([user_id, expires_at]) } model ProxmoxNode { id String @id @default(cuid()) name String hostname String @unique status NodeStatus @default(OFFLINE) cpu_cores Int @default(8) cpu_usage Float @default(0) ram_total_mb Int @default(32768) ram_used_mb Int @default(0) disk_total_gb Int @default(500) disk_used_gb Int @default(0) vm_count Int @default(0) uptime_seconds Int @default(0) pve_version String? is_connected Boolean @default(false) last_sync_at DateTime? created_at DateTime @default(now()) updated_at DateTime @updatedAt virtual_machines VirtualMachine[] placement_policies NodePlacementPolicy[] vmid_ranges VmIdRange[] health_checks ServerHealthCheck[] monitoring_alert_rules MonitoringAlertRule[] monitoring_alert_events MonitoringAlertEvent[] @@index([status]) } model BillingPlan { id String @id @default(cuid()) name String slug String @unique description String? price_monthly Decimal @db.Decimal(12, 2) price_hourly Decimal @db.Decimal(12, 4) currency Currency @default(NGN) cpu_cores Int ram_mb Int disk_gb Int bandwidth_gb Int? is_active Boolean @default(true) features Json @default("[]") created_at DateTime @default(now()) updated_at DateTime @updatedAt virtual_machines VirtualMachine[] usage_records UsageRecord[] backup_policies BackupPolicy[] @@index([is_active]) } model VirtualMachine { id String @id @default(cuid()) name String vmid Int status VmStatus @default(STOPPED) type VmType @default(QEMU) node String node_id String? tenant_id String billing_plan_id String? os_template String? cpu_cores Int @default(2) ram_mb Int @default(2048) disk_gb Int @default(40) ip_address String? cpu_usage Float @default(0) ram_usage Float @default(0) disk_usage Float @default(0) network_in Float @default(0) network_out Float @default(0) uptime_seconds Int @default(0) started_at DateTime? last_backup_at DateTime? proxmox_upid String? last_sync_at DateTime? created_at DateTime @default(now()) updated_at DateTime @updatedAt tenant Tenant @relation(fields: [tenant_id], references: [id], onDelete: Cascade) node_ref ProxmoxNode? @relation(fields: [node_id], references: [id], onDelete: SetNull) billing_plan BillingPlan? @relation(fields: [billing_plan_id], references: [id], onDelete: SetNull) usage_records UsageRecord[] backups Backup[] operation_tasks OperationTask[] power_schedules PowerSchedule[] provisioned_service ProvisionedService? snapshot_jobs SnapshotJob[] backup_restores_source BackupRestoreTask[] @relation("BackupRestoreSourceVm") backup_restores_target BackupRestoreTask[] @relation("BackupRestoreTargetVm") assigned_ip_addresses IpAddressPool[] @relation("AssignedIpVm") ip_assignments IpAssignment[] private_network_attachments PrivateNetworkAttachment[] health_checks ServerHealthCheck[] monitoring_alert_rules MonitoringAlertRule[] monitoring_alert_events MonitoringAlertEvent[] @@unique([vmid, node]) @@index([tenant_id]) @@index([node]) @@index([status]) } model ServerHealthCheck { id String @id @default(cuid()) name String description String? target_type HealthCheckTargetType check_type HealthCheckType @default(RESOURCE_THRESHOLD) tenant_id String? vm_id String? node_id String? cpu_warn_pct Float? cpu_critical_pct Float? ram_warn_pct Float? ram_critical_pct Float? disk_warn_pct Float? disk_critical_pct Float? disk_io_read_warn Float? disk_io_read_critical Float? disk_io_write_warn Float? disk_io_write_critical Float? network_in_warn Float? network_in_critical Float? network_out_warn Float? network_out_critical Float? latency_warn_ms Int? latency_critical_ms Int? schedule_minutes Int @default(5) enabled Boolean @default(true) last_run_at DateTime? next_run_at DateTime? created_by String? metadata Json @default("{}") created_at DateTime @default(now()) updated_at DateTime @updatedAt tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: SetNull) vm VirtualMachine? @relation(fields: [vm_id], references: [id], onDelete: SetNull) node ProxmoxNode? @relation(fields: [node_id], references: [id], onDelete: SetNull) results ServerHealthCheckResult[] @@index([enabled, next_run_at]) @@index([tenant_id, enabled]) @@index([vm_id, enabled]) @@index([node_id, enabled]) } model ServerHealthCheckResult { id String @id @default(cuid()) check_id String status HealthCheckStatus severity Severity @default(INFO) message String? latency_ms Int? cpu_usage Float? ram_usage Float? disk_usage Float? disk_io_read Float? disk_io_write Float? network_in Float? network_out Float? metadata Json @default("{}") checked_at DateTime @default(now()) created_at DateTime @default(now()) check ServerHealthCheck @relation(fields: [check_id], references: [id], onDelete: Cascade) @@index([check_id, checked_at]) @@index([status, checked_at]) } model MonitoringAlertRule { id String @id @default(cuid()) name String description String? tenant_id String? vm_id String? node_id String? cpu_threshold_pct Float? ram_threshold_pct Float? disk_threshold_pct Float? disk_io_read_threshold Float? disk_io_write_threshold Float? network_in_threshold Float? network_out_threshold Float? consecutive_breaches Int @default(1) evaluation_window_minutes Int @default(15) severity Severity @default(WARNING) channels Json @default("[\"IN_APP\"]") enabled Boolean @default(true) last_evaluated_at DateTime? created_by String? metadata Json @default("{}") created_at DateTime @default(now()) updated_at DateTime @updatedAt tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: SetNull) vm VirtualMachine? @relation(fields: [vm_id], references: [id], onDelete: SetNull) node ProxmoxNode? @relation(fields: [node_id], references: [id], onDelete: SetNull) events MonitoringAlertEvent[] @@index([enabled, severity]) @@index([tenant_id, enabled]) @@index([vm_id, enabled]) @@index([node_id, enabled]) } model MonitoringAlertEvent { id String @id @default(cuid()) rule_id String tenant_id String? vm_id String? node_id String? status MonitoringAlertStatus @default(OPEN) severity Severity @default(WARNING) title String message String? metric_key String? trigger_value Float? threshold_value Float? breach_count Int @default(1) resolved_at DateTime? created_at DateTime @default(now()) updated_at DateTime @updatedAt rule MonitoringAlertRule @relation(fields: [rule_id], references: [id], onDelete: Cascade) tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: SetNull) vm VirtualMachine? @relation(fields: [vm_id], references: [id], onDelete: SetNull) node ProxmoxNode? @relation(fields: [node_id], references: [id], onDelete: SetNull) notifications MonitoringAlertNotification[] @@index([rule_id, status]) @@index([status, severity, created_at]) @@index([tenant_id, status]) @@index([vm_id, status]) @@index([node_id, status]) } model MonitoringAlertNotification { id String @id @default(cuid()) alert_event_id String channel AlertChannel destination String? status AlertDispatchStatus @default(QUEUED) provider_message String? sent_at DateTime? created_at DateTime @default(now()) event MonitoringAlertEvent @relation(fields: [alert_event_id], references: [id], onDelete: Cascade) @@index([alert_event_id, status]) @@index([status, created_at]) } model IpAddressPool { id String @id @default(cuid()) address String cidr Int version IpVersion scope IpScope @default(PUBLIC) status IpAddressStatus @default(AVAILABLE) gateway String? subnet String? server String? node_id String? node_hostname String? bridge String? vlan_tag Int? sdn_zone String? tags Json @default("[]") metadata Json @default("{}") assigned_vm_id String? assigned_tenant_id String? imported_by String? imported_at DateTime @default(now()) assigned_at DateTime? returned_at DateTime? created_at DateTime @default(now()) updated_at DateTime @updatedAt assigned_vm VirtualMachine? @relation("AssignedIpVm", fields: [assigned_vm_id], references: [id], onDelete: SetNull) assigned_tenant Tenant? @relation("AssignedIpTenant", fields: [assigned_tenant_id], references: [id], onDelete: SetNull) assignments IpAssignment[] @@unique([address, cidr]) @@index([status, scope, version]) @@index([node_hostname, bridge, vlan_tag]) @@index([assigned_vm_id, status]) @@index([assigned_tenant_id, status]) } model TenantIpQuota { id String @id @default(cuid()) tenant_id String @unique ipv4_limit Int? ipv6_limit Int? reserved_ipv4 Int @default(0) reserved_ipv6 Int @default(0) burst_allowed Boolean @default(false) burst_ipv4_limit Int? burst_ipv6_limit Int? is_active Boolean @default(true) metadata Json @default("{}") created_by String? created_at DateTime @default(now()) updated_at DateTime @updatedAt tenant Tenant @relation(fields: [tenant_id], references: [id], onDelete: Cascade) @@index([is_active]) } model IpReservedRange { id String @id @default(cuid()) name String cidr String version IpVersion scope IpScope @default(PUBLIC) tenant_id String? reason String? node_hostname String? bridge String? vlan_tag Int? sdn_zone String? is_active Boolean @default(true) metadata Json @default("{}") created_by String? created_at DateTime @default(now()) updated_at DateTime @updatedAt tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: SetNull) @@index([scope, version, is_active]) @@index([tenant_id, is_active]) @@index([node_hostname, bridge, vlan_tag, is_active]) } model IpPoolPolicy { id String @id @default(cuid()) name String tenant_id String? scope IpScope? version IpVersion? node_hostname String? bridge String? vlan_tag Int? sdn_zone String? allocation_strategy IpAllocationStrategy @default(BEST_FIT) enforce_quota Boolean @default(true) disallow_reserved_use Boolean @default(true) is_active Boolean @default(true) priority Int @default(100) metadata Json @default("{}") created_by String? created_at DateTime @default(now()) updated_at DateTime @updatedAt tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: SetNull) @@index([tenant_id, is_active, priority]) @@index([scope, version, is_active]) @@index([node_hostname, bridge, vlan_tag, is_active]) } model IpAssignment { id String @id @default(cuid()) ip_address_id String vm_id String tenant_id String? assignment_type IpAssignmentType @default(ADDITIONAL) interface_name String? notes String? metadata Json @default("{}") assigned_by String? assigned_at DateTime @default(now()) released_at DateTime? is_active Boolean @default(true) created_at DateTime @default(now()) updated_at DateTime @updatedAt ip_address IpAddressPool @relation(fields: [ip_address_id], references: [id], onDelete: Cascade) vm VirtualMachine @relation(fields: [vm_id], references: [id], onDelete: Cascade) tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: SetNull) @@index([ip_address_id, is_active]) @@index([vm_id, is_active]) @@index([tenant_id, is_active]) } model PrivateNetwork { id String @id @default(cuid()) name String slug String @unique network_type PrivateNetworkType @default(VLAN) cidr String gateway String? bridge String? vlan_tag Int? sdn_zone String? server String? node_hostname String? is_private Boolean @default(true) metadata Json @default("{}") created_by String? created_at DateTime @default(now()) updated_at DateTime @updatedAt attachments PrivateNetworkAttachment[] @@index([network_type]) @@index([node_hostname, bridge, vlan_tag]) } model PrivateNetworkAttachment { id String @id @default(cuid()) network_id String vm_id String tenant_id String? interface_name String? requested_ip String? status PrivateNetworkAttachmentStatus @default(ATTACHED) metadata Json @default("{}") attached_by String? attached_at DateTime @default(now()) detached_at DateTime? created_at DateTime @default(now()) updated_at DateTime @updatedAt network PrivateNetwork @relation(fields: [network_id], references: [id], onDelete: Cascade) vm VirtualMachine @relation(fields: [vm_id], references: [id], onDelete: Cascade) tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: SetNull) @@unique([network_id, vm_id, interface_name]) @@index([vm_id, status]) @@index([tenant_id, status]) @@index([network_id, status]) } model AppTemplate { id String @id @default(cuid()) name String slug String @unique template_type TemplateType virtualization_type VmType? source String? description String? default_cloud_init String? metadata Json @default("{}") is_active Boolean @default(true) created_at DateTime @default(now()) updated_at DateTime @updatedAt groups ApplicationGroupTemplate[] provisioned_services ProvisionedService[] @@index([template_type, is_active]) } model ApplicationGroup { id String @id @default(cuid()) name String slug String @unique description String? is_active Boolean @default(true) created_at DateTime @default(now()) updated_at DateTime @updatedAt templates ApplicationGroupTemplate[] placement_policies NodePlacementPolicy[] vmid_ranges VmIdRange[] provisioned_services ProvisionedService[] @@index([is_active]) } model ApplicationGroupTemplate { id String @id @default(cuid()) group_id String template_id String priority Int @default(100) created_at DateTime @default(now()) group ApplicationGroup @relation(fields: [group_id], references: [id], onDelete: Cascade) template AppTemplate @relation(fields: [template_id], references: [id], onDelete: Cascade) @@unique([group_id, template_id]) @@index([priority]) } model NodePlacementPolicy { id String @id @default(cuid()) group_id String? node_id String? product_type ProductType? cpu_weight Int @default(40) ram_weight Int @default(30) disk_weight Int @default(20) vm_count_weight Int @default(10) max_vms Int? min_free_ram_mb Int? min_free_disk_gb Int? is_active Boolean @default(true) created_at DateTime @default(now()) updated_at DateTime @updatedAt group ApplicationGroup? @relation(fields: [group_id], references: [id], onDelete: SetNull) node ProxmoxNode? @relation(fields: [node_id], references: [id], onDelete: SetNull) @@index([group_id, is_active]) @@index([node_id, is_active]) @@index([product_type, is_active]) } model VmIdRange { id String @id @default(cuid()) node_id String? node_hostname String application_group_id String? range_start Int range_end Int next_vmid Int is_active Boolean @default(true) created_at DateTime @default(now()) updated_at DateTime @updatedAt node ProxmoxNode? @relation(fields: [node_id], references: [id], onDelete: SetNull) group ApplicationGroup? @relation(fields: [application_group_id], references: [id], onDelete: SetNull) @@unique([node_hostname, range_start, range_end]) @@index([node_hostname, is_active]) @@index([application_group_id, is_active]) } model OperationTask { id String @id @default(cuid()) task_type OperationTaskType status OperationTaskStatus @default(QUEUED) vm_id String? vm_name String? node String? requested_by String? payload Json? result Json? error_message String? proxmox_upid String? scheduled_for DateTime? started_at DateTime? completed_at DateTime? retry_count Int @default(0) created_at DateTime @default(now()) updated_at DateTime @updatedAt vm VirtualMachine? @relation(fields: [vm_id], references: [id], onDelete: SetNull) @@index([status]) @@index([task_type]) @@index([vm_id]) @@index([created_at]) } model PowerSchedule { id String @id @default(cuid()) vm_id String action PowerScheduleAction cron_expression String timezone String @default("UTC") enabled Boolean @default(true) next_run_at DateTime? last_run_at DateTime? created_by String? created_at DateTime @default(now()) updated_at DateTime @updatedAt vm VirtualMachine @relation(fields: [vm_id], references: [id], onDelete: Cascade) @@index([vm_id]) @@index([enabled, next_run_at]) } model ProvisionedService { id String @id @default(cuid()) service_group_id String? vm_id String @unique tenant_id String product_type ProductType lifecycle_status ServiceLifecycleStatus @default(ACTIVE) application_group_id String? template_id String? package_options Json @default("{}") suspended_reason String? terminated_at DateTime? created_by String? created_at DateTime @default(now()) updated_at DateTime @updatedAt vm VirtualMachine @relation(fields: [vm_id], references: [id], onDelete: Cascade) tenant Tenant @relation(fields: [tenant_id], references: [id], onDelete: Cascade) group ApplicationGroup? @relation(fields: [application_group_id], references: [id], onDelete: SetNull) template AppTemplate? @relation(fields: [template_id], references: [id], onDelete: SetNull) @@index([tenant_id, lifecycle_status]) @@index([service_group_id]) @@index([application_group_id]) } model Invoice { id String @id @default(cuid()) invoice_number String @unique tenant_id String tenant_name String? status InvoiceStatus @default(PENDING) amount Decimal @db.Decimal(14, 2) currency Currency @default(NGN) due_date DateTime paid_date DateTime? payment_provider PaymentProvider @default(MANUAL) payment_reference String? payment_url String? line_items Json @default("[]") notes String? created_at DateTime @default(now()) updated_at DateTime @updatedAt tenant Tenant @relation(fields: [tenant_id], references: [id], onDelete: Cascade) usage_records UsageRecord[] @@index([tenant_id]) @@index([status]) @@index([due_date]) } model UsageRecord { id String @id @default(cuid()) vm_id String vm_name String tenant_id String tenant_name String billing_plan_id String? plan_name String? hours_used Decimal @default(1) @db.Decimal(8, 2) price_per_hour Decimal @db.Decimal(12, 4) currency Currency @default(NGN) total_cost Decimal @db.Decimal(14, 4) period_start DateTime period_end DateTime billed Boolean @default(false) invoice_id String? cpu_hours Decimal? @db.Decimal(10, 4) ram_gb_hours Decimal? @db.Decimal(10, 4) disk_gb_hours Decimal? @db.Decimal(10, 4) created_at DateTime @default(now()) updated_at DateTime @updatedAt vm VirtualMachine @relation(fields: [vm_id], references: [id], onDelete: Cascade) tenant Tenant @relation(fields: [tenant_id], references: [id], onDelete: Cascade) billing_plan BillingPlan? @relation(fields: [billing_plan_id], references: [id], onDelete: SetNull) invoice Invoice? @relation(fields: [invoice_id], references: [id], onDelete: SetNull) @@index([vm_id]) @@index([tenant_id]) @@index([period_start]) @@index([billed]) @@unique([vm_id, period_start, period_end]) } model Backup { id String @id @default(cuid()) vm_id String vm_name String tenant_id String? node String? status BackupStatus @default(PENDING) type BackupType @default(FULL) source BackupSource @default(LOCAL) size_mb Float? storage String? backup_path String? pbs_snapshot_id String? route_key String? is_protected Boolean @default(false) restore_enabled Boolean @default(true) total_files Int? schedule BackupSchedule @default(MANUAL) retention_days Int @default(7) snapshot_job_id String? started_at DateTime? completed_at DateTime? next_run_at DateTime? expires_at DateTime? notes String? proxmox_upid String? created_at DateTime @default(now()) updated_at DateTime @updatedAt vm VirtualMachine @relation(fields: [vm_id], references: [id], onDelete: Cascade) tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: SetNull) snapshot_job SnapshotJob? @relation(fields: [snapshot_job_id], references: [id], onDelete: SetNull) restore_tasks BackupRestoreTask[] @@index([vm_id]) @@index([tenant_id]) @@index([status]) @@index([next_run_at]) @@index([snapshot_job_id]) } model BackupPolicy { id String @id @default(cuid()) tenant_id String? billing_plan_id String? max_files Int @default(10) max_total_size_mb Float @default(51200) max_protected_files Int @default(3) allow_file_restore Boolean @default(true) allow_cross_vm_restore Boolean @default(true) allow_pbs_restore Boolean @default(true) created_at DateTime @default(now()) updated_at DateTime @updatedAt tenant Tenant? @relation(fields: [tenant_id], references: [id], onDelete: Cascade) billing_plan BillingPlan? @relation(fields: [billing_plan_id], references: [id], onDelete: Cascade) @@index([tenant_id]) @@index([billing_plan_id]) } model BackupRestoreTask { id String @id @default(cuid()) backup_id String source_vm_id String target_vm_id String mode BackupRestoreMode requested_files Json @default("[]") pbs_enabled Boolean @default(false) status BackupRestoreStatus @default(PENDING) result_message String? started_at DateTime? completed_at DateTime? created_by String? created_at DateTime @default(now()) updated_at DateTime @updatedAt backup Backup @relation(fields: [backup_id], references: [id], onDelete: Cascade) source_vm VirtualMachine @relation("BackupRestoreSourceVm", fields: [source_vm_id], references: [id], onDelete: Cascade) target_vm VirtualMachine @relation("BackupRestoreTargetVm", fields: [target_vm_id], references: [id], onDelete: Cascade) @@index([backup_id]) @@index([source_vm_id]) @@index([target_vm_id]) @@index([status]) } model SnapshotJob { id String @id @default(cuid()) vm_id String name String frequency SnapshotFrequency @default(DAILY) interval Int @default(1) day_of_week Int? hour_utc Int @default(2) minute_utc Int @default(0) retention Int @default(7) enabled Boolean @default(true) next_run_at DateTime? last_run_at DateTime? created_by String? created_at DateTime @default(now()) updated_at DateTime @updatedAt vm VirtualMachine @relation(fields: [vm_id], references: [id], onDelete: Cascade) backups Backup[] @@index([vm_id]) @@index([enabled, next_run_at]) } model AuditLog { id String @id @default(cuid()) action String resource_type ResourceType resource_id String? resource_name String? actor_email String actor_role String? severity Severity @default(INFO) details Json? ip_address String? created_at DateTime @default(now()) @@index([resource_type]) @@index([severity]) @@index([created_at]) } model SecurityEvent { id String @id @default(cuid()) event_type String severity Severity @default(WARNING) status SecurityStatus @default(OPEN) source_ip String? source_country String? target_vm_id String? node String? description String? details Json? resolved_at DateTime? resolved_by String? created_at DateTime @default(now()) updated_at DateTime @updatedAt @@index([status]) @@index([severity]) @@index([created_at]) } model FirewallRule { id String @id @default(cuid()) name String direction Direction @default(INBOUND) action FirewallAction @default(DENY) protocol Protocol @default(TCP) source_ip String? destination_ip String? port_range String? priority Int @default(100) enabled Boolean @default(true) applies_to AppliesTo @default(ALL_VMS) target_id String? hit_count Int @default(0) description String? created_at DateTime @default(now()) updated_at DateTime @updatedAt @@index([enabled]) @@index([priority]) } model Setting { id String @id @default(cuid()) key String @unique type SettingType @default(GENERAL) value Json is_encrypted Boolean @default(false) created_at DateTime @default(now()) updated_at DateTime @updatedAt } model CmsPage { id String @id @default(cuid()) slug String @unique title String section String @default("general") content Json @default("{}") is_published Boolean @default(false) created_by String? updated_by String? created_at DateTime @default(now()) updated_at DateTime @updatedAt @@index([section, is_published]) } model SiteNavigationItem { id String @id @default(cuid()) label String href String position String @default("header") sort_order Int @default(100) is_enabled Boolean @default(true) metadata Json @default("{}") created_at DateTime @default(now()) updated_at DateTime @updatedAt @@index([position, sort_order]) }