Files
proxpanel/backend/prisma/schema.prisma

1282 lines
35 KiB
Plaintext

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])
}