Files
flatrender/services/render/internal/models/models.go
T
soroush.asadi 0a7dd9b84c
Build backend images / build content-svc (push) Failing after 45s
Build backend images / build file-svc (push) Failing after 55s
Build backend images / build gateway (push) Failing after 53s
Build backend images / build identity-svc (push) Failing after 54s
Build backend images / build notification-svc (push) Failing after 53s
Build backend images / build render-svc (push) Failing after 47s
Build backend images / build studio-svc (push) Failing after 51s
feat(nodes): live CPU/RAM/disk monitoring in the node list
- node-agent: internal/metrics — read CPU% (GetSystemTimes), RAM (GlobalMemoryStatusEx),
  disk used%/total (GetDiskFreeSpaceEx) via stdlib kernel32 (no external dep; windows
  build + non-windows stub). Heartbeat now reports cpu_pct/ram_available_mb/disk_used_pct/
  disk_total_gb + ae_running.
- render-svc: heartbeat persists last_disk_pct + disk_total_gb (migration 29); RenderNode
  model + node SELECT/scan carry them.
- admin: rewrite NodesTable to the real RenderNode shape (fixes a pre-existing items/V2Node
  mismatch that left the list empty) + a CPU/RAM/disk bars column + stale-heartbeat flag.
- assets-bundle ingestion: ProjectMediaBundle (jszip) auto-maps project.zip → project/scene
  image/demo/colour + music; PatchProject gains image/full_demo/shared_colors_svg.
- scan: RGBA (4-number) colours recognised + frshare single-int controls detected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:01:18 +03:30

468 lines
19 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
)
// ── Enums ────────────────────────────────────────────────────────────────────
const (
// NodeStatus
NodeStatusReady = "Ready"
NodeStatusBusy = "Busy"
NodeStatusOffline = "Offline"
NodeStatusMaintenance = "Maintenance"
NodeStatusCrashed = "Crashed"
NodeStatusUpdating = "Updating"
NodeStatusDisabled = "Disabled"
// NodeKind
NodeKindShared = "Shared"
NodeKindDedicated = "Dedicated"
NodeKindSpot = "Spot"
// RenderStep
StepQueued = "Queued"
StepPreparing = "Preparing"
StepTemplateCache = "TemplateCache"
StepJsxGen = "JsxGen"
StepMusic = "Music"
StepRendering = "Rendering"
StepValidating = "Validating"
StepRepairing = "Repairing"
StepOptimisation = "Optimisation"
StepVideo = "Video"
StepMixing = "Mixing"
StepFinal = "Final"
StepUploading = "Uploading"
StepDone = "Done"
StepFailed = "Failed"
StepCancelled = "Cancelled"
// PriceKind
PriceKindFree = "Free"
PriceKindPreview = "Preview"
PriceKindCash = "Cash"
PriceKindPlan = "Plan"
PriceKindSnapshot = "Snapshot"
PriceKindReseller = "Reseller"
// RenderQuality
QualityLow = "Low"
QualityMedium = "Medium"
QualityHigh = "High"
QualityFull = "Full"
QualityLossless = "Lossless"
// FrameJobStatus
FrameJobPending = "Pending"
FrameJobRendering = "Rendering"
FrameJobValidated = "Validated"
FrameJobRepairing = "Repairing"
FrameJobConverting = "Converting"
FrameJobDone = "Done"
FrameJobFailed = "Failed"
// PriorityQueue
QueueSnapshot = "snapshot"
QueueVIP = "vip"
QueuePaid = "paid"
QueuePreview = "preview"
QueueMockup = "mockup"
QueueVoiceover = "voiceover"
// Export types
ExportCreateRender = "Render"
ExportCreateUpload = "Upload"
ExportCreateSnapshot = "Snapshot"
ExportCreateReupload = "Reupload"
ExportFileVideo = "Video"
ExportFileImage = "Image"
ExportFileAudio = "Audio"
ExportFileGIF = "GIF"
ExportFilePDF = "PDF"
)
// ── Domain entities ──────────────────────────────────────────────────────────
type RenderNode struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Region string `json:"region"`
NodeIP string `json:"node_ip"`
WorkerPort int `json:"worker_port"`
PublicEndpoint *string `json:"public_endpoint,omitempty"`
RamGB *int `json:"ram_gb,omitempty"`
CPUCores *int `json:"cpu_cores,omitempty"`
GPUModel *string `json:"gpu_model,omitempty"`
StorageGB *int `json:"storage_gb,omitempty"`
CurrentAEVersion string `json:"current_ae_version"`
AvailableAEVersions []string `json:"available_ae_versions"`
NodeAgentVersion *string `json:"node_agent_version,omitempty"`
NodeKind string `json:"node_kind"`
OwnerUserID *uuid.UUID `json:"owner_user_id,omitempty"`
OwnerTenantID *uuid.UUID `json:"owner_tenant_id,omitempty"`
Status string `json:"status"`
CurrentJobID *uuid.UUID `json:"current_job_id,omitempty"`
CurrentFrameJobID *uuid.UUID `json:"current_frame_job_id,omitempty"`
JobStartedAt *time.Time `json:"job_started_at,omitempty"`
LastHeartbeatAt *time.Time `json:"last_heartbeat_at,omitempty"`
LastCPUPct *int `json:"last_cpu_pct,omitempty"`
LastRAMAvailableMB *int `json:"last_ram_available_mb,omitempty"`
LastDiskPct *int `json:"last_disk_pct,omitempty"`
DiskTotalGB *int `json:"disk_total_gb,omitempty"`
AERunning bool `json:"ae_running"`
LifetimeTaskCount int64 `json:"lifetime_task_count"`
LifetimeCrashCount int `json:"lifetime_crash_count"`
ConsecutiveFailures int `json:"consecutive_failures"`
Priority int `json:"priority"`
IsActive bool `json:"is_active"`
AcceptsNewJobs bool `json:"accepts_new_jobs"`
LastMaintenanceAt *time.Time `json:"last_maintenance_at,omitempty"`
NextMaintenanceAt *time.Time `json:"next_maintenance_at,omitempty"`
MaintenanceReason *string `json:"maintenance_reason,omitempty"`
CachedTemplateMD5s []string `json:"cached_template_md5s"`
CacheUsedGB int `json:"cache_used_gb"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type RenderJob struct {
ID uuid.UUID `json:"id"`
TenantID uuid.UUID `json:"tenant_id"`
UserID uuid.UUID `json:"user_id"`
SavedProjectID uuid.UUID `json:"saved_project_id"`
OriginalProjectID uuid.UUID `json:"original_project_id"`
ProjectName *string `json:"project_name,omitempty"`
Title *string `json:"title,omitempty"`
Name *string `json:"name,omitempty"`
ExternalJobID *string `json:"external_job_id,omitempty"`
PriorityQueue string `json:"priority_queue"`
PriorityScore int `json:"priority_score"`
Step string `json:"step"`
RenderProgress int `json:"render_progress"`
ConvertProgress int `json:"convert_progress"`
ImagePreviewB64 *string `json:"image_preview_b64,omitempty"`
PriceType string `json:"price_type"`
PaidPriceMinor int64 `json:"paid_price_minor"`
DiscountCode *string `json:"discount_code,omitempty"`
SupportFlatrender bool `json:"support_flatrender"`
Mode string `json:"mode"`
Quality string `json:"quality"`
Resolution string `json:"resolution"`
RHeight int `json:"r_height"`
FrameRate int `json:"frame_rate"`
Is60FPS bool `json:"is_60_fps"`
DurationSec float64 `json:"duration_sec"`
ExportDurationSec *float64 `json:"export_duration_sec,omitempty"`
HasMusic bool `json:"has_music"`
HasSFX bool `json:"has_sfx"`
HasVoiceover bool `json:"has_voiceover"`
MusicVolume *float64 `json:"music_volume,omitempty"`
SFXVolume *float64 `json:"sfx_volume,omitempty"`
VoiceoverVolume *float64 `json:"voiceover_volume,omitempty"`
RenderNodeCount int `json:"render_node_count"`
CurrentActiveNodes int `json:"current_active_nodes"`
Region *string `json:"region,omitempty"`
TellMeWhenDone bool `json:"tell_me_when_done"`
RetryCount int `json:"retry_count"`
MaxRetries int `json:"max_retries"`
RepairAttempts int `json:"repair_attempts"`
FailedMessage *string `json:"failed_message,omitempty"`
FailedAtStep *string `json:"failed_at_step,omitempty"`
ExportID *uuid.UUID `json:"export_id,omitempty"`
TaskStartDate time.Time `json:"task_start_date"`
QueuedAt time.Time `json:"queued_at"`
StartedAt *time.Time `json:"started_at,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type FrameJob struct {
ID uuid.UUID `json:"id"`
RenderJobID uuid.UUID `json:"render_job_id"`
NodeID uuid.UUID `json:"node_id"`
StartFrame int `json:"start_frame"`
EndFrame int `json:"end_frame"`
CollectFrameCount int `json:"collect_frame_count"`
OrderValue int `json:"order_value"`
FolderName string `json:"folder_name"`
ConvertURL *string `json:"convert_url,omitempty"`
Status string `json:"status"`
FramesRendered int `json:"frames_rendered"`
FramesValidated int `json:"frames_validated"`
Attempt int `json:"attempt"`
LastError *string `json:"last_error,omitempty"`
OutputMP4URL *string `json:"output_mp4_url,omitempty"`
AssignedAt time.Time `json:"assigned_at"`
StartedAt *time.Time `json:"started_at,omitempty"`
LastProgressAt *time.Time `json:"last_progress_at,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type Snapshot struct {
ID uuid.UUID `json:"id"`
TenantID uuid.UUID `json:"tenant_id"`
UserID uuid.UUID `json:"user_id"`
SavedProjectID uuid.UUID `json:"saved_project_id"`
SceneKey string `json:"scene_key"`
FrameNumber int `json:"frame_number"`
InputsHash string `json:"inputs_hash"`
Status string `json:"status"`
RenderNodeID *uuid.UUID `json:"render_node_id,omitempty"`
ImageURL *string `json:"image_url,omitempty"`
ThumbnailURL *string `json:"thumbnail_url,omitempty"`
Width *int `json:"width,omitempty"`
Height *int `json:"height,omitempty"`
SizeBytes *int64 `json:"size_bytes,omitempty"`
RequestedAt time.Time `json:"requested_at"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
DurationMS *int `json:"duration_ms,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
ErrorMessage *string `json:"error_message,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
type Export struct {
ID uuid.UUID `json:"id"`
TenantID uuid.UUID `json:"tenant_id"`
UserID uuid.UUID `json:"user_id"`
SavedProjectID uuid.UUID `json:"saved_project_id"`
ProjectID uuid.UUID `json:"project_id"`
RenderJobID *uuid.UUID `json:"render_job_id,omitempty"`
Image *string `json:"image,omitempty"`
Path string `json:"path"`
FileExtension string `json:"file_extension"`
FileType string `json:"file_type"`
RenderQuality string `json:"render_quality"`
CreateType string `json:"create_type"`
SizeBytes int64 `json:"size_bytes"`
DurationSec *float64 `json:"duration_sec,omitempty"`
Width *int `json:"width,omitempty"`
Height *int `json:"height,omitempty"`
ProduceDate time.Time `json:"produce_date"`
AutoDeleteDate time.Time `json:"auto_delete_date"`
DeleteNotified bool `json:"delete_notified"`
CreatedAt time.Time `json:"created_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}
type ExportFile struct {
ID uuid.UUID `json:"id"`
ExportID uuid.UUID `json:"export_id"`
UserID uuid.UUID `json:"user_id"`
Name *string `json:"name,omitempty"`
Thumbnail *string `json:"thumbnail,omitempty"`
Path string `json:"path"`
SizeBytes int64 `json:"size_bytes"`
FileType string `json:"file_type"`
Width *int `json:"width,omitempty"`
Height *int `json:"height,omitempty"`
Sort int `json:"sort"`
CreatedAt time.Time `json:"created_at"`
}
type NodeHealthLog struct {
ID int64 `json:"id"`
NodeID uuid.UUID `json:"node_id"`
RecordedAt time.Time `json:"recorded_at"`
Status string `json:"status"`
CPUPct *int `json:"cpu_pct,omitempty"`
RAMAvailableMB *int `json:"ram_available_mb,omitempty"`
AERunning *bool `json:"ae_running,omitempty"`
CurrentJobID *uuid.UUID `json:"current_job_id,omitempty"`
CurrentFrame *int `json:"current_frame,omitempty"`
CacheUsedGB *int `json:"cache_used_gb,omitempty"`
}
type NodeCrash struct {
ID uuid.UUID `json:"id"`
NodeID uuid.UUID `json:"node_id"`
RenderJobID *uuid.UUID `json:"render_job_id,omitempty"`
FrameJobID *uuid.UUID `json:"frame_job_id,omitempty"`
CrashedAt time.Time `json:"crashed_at"`
LastKnownFrame *int `json:"last_known_frame,omitempty"`
CrashSignal *string `json:"crash_signal,omitempty"`
ErrorLog *string `json:"error_log,omitempty"`
LogFileURL *string `json:"log_file_url,omitempty"`
AutoRecovered bool `json:"auto_recovered"`
RecoveryAction *string `json:"recovery_action,omitempty"`
RecoveredAt *time.Time `json:"recovered_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
type NodeUpdate struct {
ID uuid.UUID `json:"id"`
UpdateFileName string `json:"update_file_name"`
UpdateNumber int `json:"update_number"`
Description *string `json:"description,omitempty"`
TargetAEVersion *string `json:"target_ae_version,omitempty"`
InUpdateQueue bool `json:"in_update_queue"`
RolledOutToNodeIDs []uuid.UUID `json:"rolled_out_to_node_ids"`
LastUpdateQueueDate *time.Time `json:"last_update_queue_date,omitempty"`
CreateDate time.Time `json:"create_date"`
CreatedAt time.Time `json:"created_at"`
}
// ── Request / Response types ─────────────────────────────────────────────────
type PagedResponse[T any] struct {
Data []T `json:"data"`
Meta PaginationMeta `json:"meta"`
}
type PaginationMeta struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Total int64 `json:"total"`
HasMore bool `json:"has_more"`
}
type RenderJobCreateRequest struct {
SavedProjectID uuid.UUID `json:"saved_project_id" binding:"required"`
Quality string `json:"quality" binding:"required"`
Resolution string `json:"resolution" binding:"required"`
FrameRate *int `json:"frame_rate"`
Is60FPS *bool `json:"is_60_fps"`
PriceType *string `json:"price_type"`
DiscountCode *string `json:"discount_code"`
SupportFlatrender *bool `json:"support_flatrender"`
TellMeWhenDone *bool `json:"tell_me_when_done"`
PreferredRegion *string `json:"preferred_region"`
}
type SnapshotCreateRequest struct {
SavedProjectID uuid.UUID `json:"saved_project_id" binding:"required"`
SceneKey string `json:"scene_key" binding:"required"`
FrameNumber int `json:"frame_number" binding:"min=0"`
}
type NodeCreateRequest struct {
Name string `json:"name" binding:"required"`
Region string `json:"region" binding:"required"`
NodeIP string `json:"node_ip" binding:"required"`
WorkerPort int `json:"worker_port" binding:"required"`
CurrentAEVersion string `json:"current_ae_version" binding:"required"`
RamGB *int `json:"ram_gb"`
CPUCores *int `json:"cpu_cores"`
NodeKind *string `json:"node_kind"`
OwnerUserID *uuid.UUID `json:"owner_user_id"`
Priority *int `json:"priority"`
}
type NodePatchRequest struct {
Priority *int `json:"priority"`
IsActive *bool `json:"is_active"`
AcceptsNewJobs *bool `json:"accepts_new_jobs"`
NodeKind *string `json:"node_kind"`
OwnerUserID *uuid.UUID `json:"owner_user_id"`
NextMaintenanceAt *time.Time `json:"next_maintenance_at"`
MaintenanceReason *string `json:"maintenance_reason"`
}
type NodeHeartbeatRequest struct {
NodeID uuid.UUID `json:"node_id"`
Status string `json:"status"`
CPUPct *int `json:"cpu_pct"`
RAMAvailableMB *int `json:"ram_available_mb"`
DiskUsedPct *int `json:"disk_used_pct"`
DiskTotalGB *int `json:"disk_total_gb"`
AERunning *bool `json:"ae_running"`
CurrentJobID *uuid.UUID `json:"current_job_id"`
CurrentFrame *int `json:"current_frame"`
CacheUsedGB *int `json:"cache_used_gb"`
}
type NodeOnlineRequest struct {
NodeAgentVersion string `json:"node_agent_version"`
CurrentAEVersion string `json:"current_ae_version"`
AvailableAEVersions []string `json:"available_ae_versions"`
RamGB *int `json:"ram_gb"`
CPUCores *int `json:"cpu_cores"`
CacheUsedGB *int `json:"cache_used_gb"`
CachedTemplateMD5s []string `json:"cached_template_md5s"`
}
type FrameProgressRequest struct {
FrameJobID uuid.UUID `json:"frame_job_id" binding:"required"`
FrameNumber int `json:"frame_number"`
FileSizeBytes *int64 `json:"file_size_bytes"`
CompletedAt *time.Time `json:"completed_at"`
}
type CrashReportRequest struct {
NodeID uuid.UUID `json:"node_id" binding:"required"`
FrameJobID *uuid.UUID `json:"frame_job_id"`
LastKnownFrame *int `json:"last_known_frame"`
CrashSignal *string `json:"crash_signal"`
AEVersion *string `json:"ae_version"`
ErrorLogTail *string `json:"error_log_tail"`
LogFileURL *string `json:"log_file_url"`
}
type ClaimJobRequest struct {
NodeID uuid.UUID `json:"node_id" binding:"required"`
Region string `json:"region"`
}
type ClaimedJob struct {
JobID uuid.UUID `json:"job_id"`
SavedProjectID uuid.UUID `json:"saved_project_id"`
Quality string `json:"quality"`
Resolution string `json:"resolution"`
FrameRate int `json:"frame_rate"`
HasMusic bool `json:"has_music"`
HasVoiceover bool `json:"has_voiceover"`
// AEPDownloadURL is a presigned MinIO GET URL for the .aep project file
// (or .zip bundle). Valid for 2 hours. Empty when the template is not yet uploaded.
AEPDownloadURL string `json:"aep_download_url,omitempty"`
// IsBundle is true when AEPDownloadURL points to a .zip bundle (the .aep plus
// footage/fonts) that the node agent must extract before rendering.
IsBundle bool `json:"is_bundle,omitempty"`
// BundleMD5 is the stored object's ETag/MD5 — the node uses it as a cache key so
// repeated renders of the same template download + extract the bundle only once.
BundleMD5 string `json:"bundle_md5,omitempty"`
}
// OutputUploadURLResponse is returned by POST .../output-upload-url.
type OutputUploadURLResponse struct {
ExportID uuid.UUID `json:"export_id"`
UploadURL string `json:"upload_url"`
ObjectKey string `json:"object_key"`
ExpiresAt time.Time `json:"expires_at"`
}
type CacheUpdateRequest struct {
Action string `json:"action" binding:"required"`
ProjectID *uuid.UUID `json:"project_id"`
AEPFileMD5 string `json:"aep_file_md5" binding:"required"`
FileSizeBytes *int64 `json:"file_size_bytes"`
CacheUsedGB *int `json:"cache_used_gb"`
ErrorMessage *string `json:"error_message"`
}
type ReplicaReadyRequest struct {
NodeID uuid.UUID `json:"node_id" binding:"required"`
ReplicaPath string `json:"replica_path" binding:"required"`
ReplicaMD5 *string `json:"replica_md5"`
}
type APIError struct {
Code string `json:"code"`
Message string `json:"message"`
}
// Claims carries JWT payload
type Claims struct {
UserID uuid.UUID `json:"sub"`
TenantID uuid.UUID `json:"tenant_id"`
IsAdmin bool `json:"is_admin"`
Role string `json:"role"`
}