classes/UnraidClasses.ps1

class UnraidAuthException : System.Exception {
    UnraidAuthException([string]$message) : base($message) {}
    UnraidAuthException([string]$message, [System.Exception]$innerException) : base($message, $innerException) {}
}

class UnraidSessionException : System.Exception {
    UnraidSessionException([string]$message) : base($message) {}
    UnraidSessionException([string]$message, [System.Exception]$innerException) : base($message, $innerException) {}
}

class UnraidApiException : System.Exception {
    UnraidApiException([string]$message) : base($message) {}
    UnraidApiException([string]$message, [System.Exception]$innerException) : base($message, $innerException) {}
}

class UnraidSession {
    [string]$Server
    [string]$Uri
    [string]$UserName
    [string]$CsrfToken 
    [string]$ApiKey
    [object]$WebSession
    [datetime]$ConnectedAt
    [bool]$SkipCertificateCheck
    [string]$AuthType

    UnraidSession($Server, $Uri, $UserName, $CsrfToken, $WebSession, $SkipCert) {
        $this.Server = $Server
        $this.Uri = $Uri
        $this.UserName = $UserName
        $this.CsrfToken = $CsrfToken.ToString()
        $this.WebSession = $WebSession
        $this.ConnectedAt = [DateTime]::Now
        $this.SkipCertificateCheck = $SkipCert
        $this.AuthType = 'Credential'
        $this.ApiKey = $null
    }

    UnraidSession($Server, $Uri, $ApiKey, $SkipCert) {
        $this.Server = $Server
        $this.Uri = $Uri
        $this.ApiKey = $ApiKey
        $this.ConnectedAt = [DateTime]::Now
        $this.SkipCertificateCheck = $SkipCert
        $this.AuthType = 'ApiKey'
        $this.UserName = 'API Key'
        $this.CsrfToken = $null
        $this.WebSession = $null
    }

    [string] ToString() {
        $authInfo = if ($this.AuthType -eq 'ApiKey') { 'API Key' } else { $this.UserName }
        return "UnraidSession: [$authInfo @ $($this.Server)]"
    }
}

class UnraidSizeFix {

    static [string] FormatBytes([double]$rawBytes) {
        if ($rawBytes -eq 0) { return "0 B" }
        if ($rawBytes -ge 1PB) { return "$([math]::Round($rawBytes / 1PB, 2)) PB" }
        if ($rawBytes -ge 1TB) { return "$([math]::Round($rawBytes / 1TB, 2)) TB" }
        if ($rawBytes -ge 1GB) { return "$([math]::Round($rawBytes / 1GB, 2)) GB" }
        if ($rawBytes -ge 1MB) { return "$([math]::Round($rawBytes / 1MB, 2)) MB" }
        if ($rawBytes -ge 1KB) { return "$([math]::Round($rawBytes / 1KB, 2)) KB" }
        return "$rawBytes B"
    }

    static [string] FormatTemperature([string]$tempValue) {
        if (!$tempValue) { return '' }
        
        # Strip garbage chars, leave numbers/dots/dashes which sometimes appear
        $cleanedTemperature = $tempValue -replace '[^\d.-]', ''
        
        if ($cleanedTemperature -match '^-?\d+\.?\d*$') {
            return "${cleanedTemperature}$([char]0x00B0)C"
        }
        return "${tempValue}$([char]0x00B0)C"
    }
}


class UnraidDataDisk {
    [string]$Name
    [string]$Device
    [string]$Size
    [string]$Used
    [string]$Free
    [string]$FileSystem
    [string]$Temperature
    [string]$Status
    [int]$Errors
    [bool]$Rotational
    [long]$NumReads
    [long]$NumWrites
    [bool]$Exportable
    [string]$Transport

    UnraidDataDisk([object]$diskObject) {
        $this.Name = $diskObject.name
        $this.Device = $diskObject.device
        $this.Size = [UnraidSizeFix]::FormatBytes([double]$diskObject.size * 1KB)
        $this.FileSystem = $diskObject.fsType
        $this.Status = $diskObject.status
        $this.Errors = $diskObject.numErrors
        $this.Rotational = $diskObject.rotational

        if ($diskObject.numReads) { $this.NumReads = $diskObject.numReads }
        if ($diskObject.numWrites) { $this.NumWrites = $diskObject.numWrites }
        if ($null -ne $diskObject.exportable) { $this.Exportable = [bool]$diskObject.exportable }
        if ($diskObject.transport) { $this.Transport = $diskObject.transport }

        if ($diskObject.fsUsed) {
            $this.Used = [UnraidSizeFix]::FormatBytes([double]$diskObject.fsUsed * 1KB)
        }
        if ($diskObject.fsFree) {
            $this.Free = [UnraidSizeFix]::FormatBytes([double]$diskObject.fsFree * 1KB)
        }
        if ($diskObject.temp) {
            $this.Temperature = [UnraidSizeFix]::FormatTemperature($diskObject.temp)
        }
    }
}

class UnraidParityDisk {
    [string]$Name
    [string]$Device
    [string]$Size
    [string]$Temperature
    [string]$Status
    [int]$Errors
    [long]$NumReads
    [long]$NumWrites
    [bool]$Exportable
    [string]$Transport

    UnraidParityDisk([object]$diskObject) {
        $this.Name = $diskObject.name
        $this.Device = $diskObject.device
        $this.Size = [UnraidSizeFix]::FormatBytes([double]$diskObject.size * 1KB)
        $this.Status = $diskObject.status
        $this.Errors = $diskObject.numErrors
        
        if ($diskObject.numReads) { $this.NumReads = $diskObject.numReads }
        if ($diskObject.numWrites) { $this.NumWrites = $diskObject.numWrites }
        if ($null -ne $diskObject.exportable) { $this.Exportable = [bool]$diskObject.exportable }
        if ($diskObject.transport) { $this.Transport = $diskObject.transport }
        
        if ($diskObject.temp) {
            $this.Temperature = [UnraidSizeFix]::FormatTemperature($diskObject.temp)
        }
    }
}

class UnraidCacheDisk {
    [string]$Name
    [string]$Device
    [string]$Size
    [string]$Used
    [string]$Free
    [string]$Temperature
    [string]$Status
    [int]$Errors
    [bool]$Rotational
    [long]$NumReads
    [long]$NumWrites
    [bool]$Exportable
    [string]$Transport

    UnraidCacheDisk([object]$diskObject) {
        $this.Name = $diskObject.name
        $this.Device = $diskObject.device
        $this.Size = [UnraidSizeFix]::FormatBytes([double]$diskObject.size * 1KB)
        $this.Status = $diskObject.status
        
        if ($diskObject.numErrors) { $this.Errors = $diskObject.numErrors }
        if ($null -ne $diskObject.rotational) { $this.Rotational = $diskObject.rotational }
        if ($diskObject.numReads) { $this.NumReads = $diskObject.numReads }
        if ($diskObject.numWrites) { $this.NumWrites = $diskObject.numWrites }
        if ($null -ne $diskObject.exportable) { $this.Exportable = [bool]$diskObject.exportable }
        if ($diskObject.transport) { $this.Transport = $diskObject.transport }
        
        if ($diskObject.fsUsed) {
            $this.Used = [UnraidSizeFix]::FormatBytes([double]$diskObject.fsUsed * 1KB)
        }
        if ($diskObject.fsFree) {
            $this.Free = [UnraidSizeFix]::FormatBytes([double]$diskObject.fsFree * 1KB)
        }
        if ($diskObject.temp) {
            $this.Temperature = [UnraidSizeFix]::FormatTemperature($diskObject.temp)
        }
    }
}

class UnraidArray {
    [string]$State
    [string]$TotalSize
    [string]$UsedSpace
    [string]$FreeSpace
    [int]$DiskCount
    [string]$ParityStatus
    [string]$ParityProgress
    [string]$ParitySpeed
    [int]$ParityErrors
    [int]$ParityCorrecting
    [datetime]$ParityCheckDate
    [int]$ParityCheckDuration

    [UnraidDataDisk[]]$DataDisks
    [UnraidParityDisk[]]$ParityDisks
    [UnraidCacheDisk[]]$CacheDisks

    UnraidArray([object]$arrayData) {
        $this.State = $arrayData.state

        if ($arrayData.capacity -and $arrayData.capacity.kilobytes) {
            $kilobytesTotal = [double]$arrayData.capacity.kilobytes.total
            $kilobytesUsed = [double]$arrayData.capacity.kilobytes.used
            $kilobytesFree = [double]$arrayData.capacity.kilobytes.free

            $this.TotalSize = [UnraidSizeFix]::FormatBytes($kilobytesTotal * 1KB)
            $this.UsedSpace = [UnraidSizeFix]::FormatBytes($kilobytesUsed * 1KB)
            $this.FreeSpace = [UnraidSizeFix]::FormatBytes($kilobytesFree * 1KB)
            
            # If API says STARTED but we have 0 bytes capacity, the array is NOT actually mounted or
            # the API is out of sync (which seems to happen somewhat frequently)
            # It is likely in a 'Starting' limbo or unmounted state.
            if ($this.State -eq "STARTED" -and $kilobytesTotal -eq 0) {
                $this.State = "STARTED (UNMOUNTED)"
            }
        }

        if ($arrayData.capacity -and $arrayData.capacity.disks) {
            $this.DiskCount = $arrayData.capacity.disks.total
        }

        if ($arrayData.parityCheckStatus) {
            $this.ParityStatus = $arrayData.parityCheckStatus.status
            $this.ParityProgress = "$($arrayData.parityCheckStatus.progress)%"
            
            if ($null -ne $arrayData.parityCheckStatus.errors) { $this.ParityErrors = $arrayData.parityCheckStatus.errors }
            if ($null -ne $arrayData.parityCheckStatus.correcting) { $this.ParityCorrecting = $arrayData.parityCheckStatus.correcting }
            if ($arrayData.parityCheckStatus.date) { $this.ParityCheckDate = $arrayData.parityCheckStatus.date }
            if ($arrayData.parityCheckStatus.duration) { $this.ParityCheckDuration = $arrayData.parityCheckStatus.duration }
            
            if ($arrayData.parityCheckStatus.speed -and $arrayData.parityCheckStatus.speed -gt 0) {
                $this.ParitySpeed = [UnraidSizeFix]::FormatBytes($arrayData.parityCheckStatus.speed) + "/s"
            }
            else {
                $this.ParitySpeed = "0 B/s"
            }
        }

        if ($arrayData.disks) {
            $this.DataDisks = $arrayData.disks | ForEach-Object { [UnraidDataDisk]::new($_) }
        }
        
        if ($arrayData.parities) {
            $this.ParityDisks = $arrayData.parities | ForEach-Object { [UnraidParityDisk]::new($_) }
        }
        
        if ($arrayData.caches) {
            $this.CacheDisks = $arrayData.caches | ForEach-Object { [UnraidCacheDisk]::new($_) }
        }
    }
}
class UnraidContainer {
    [string]$Id
    [string]$Name
    [string]$Image
    [string]$ImageId
    [string]$Command
    [string]$Created
    [long]$SizeRootFs
    [string]$State
    [string]$Status
    [bool]$AutoStart
    [string]$NetworkMode
    [string]$IPAddress
    [string]$WebUI
    [string]$PortSummary
    [string[]]$VolumeMappings
    [object]$Labels

    UnraidContainer($containerData, $serverIpAddress) {
        $this.Id = $containerData.id
        $this.Image = $containerData.image
        $this.ImageId = $containerData.imageId
        $this.Command = $containerData.command
        $this.Created = $containerData.created
        $this.State = $containerData.state
        $this.Status = $containerData.status
        $this.AutoStart = $containerData.autoStart

        if ($containerData.sizeRootFs) { $this.SizeRootFs = $containerData.sizeRootFs }

        if ($containerData.names) {
            $this.Name = $containerData.names[0].TrimStart('/')
        }

        if ($containerData.hostConfig) {
            $this.NetworkMode = $containerData.hostConfig.networkMode
        }

        $this.IPAddress = $serverIpAddress

        if ($this.NetworkMode -notin @("host", "bridge") -and $containerData.networkSettings.Networks) {
            try {
                $containerNetworkSettings = $containerData.networkSettings.Networks
                if ($containerNetworkSettings -is [string]) { 
                    $containerNetworkSettings = $containerNetworkSettings | ConvertFrom-Json 
                }

                foreach ($networkProperty in $containerNetworkSettings.PSObject.Properties) {
                    $networkDetails = $networkProperty.Value
                    if ($networkDetails.IPAddress) {
                        $this.IPAddress = $networkDetails.IPAddress
                        if ($this.NetworkMode -eq $networkProperty.Name) { 
                            break 
                        }
                    }
                }
            }
            catch {
                # Parsing failed, default to server IP
            }
        }

        if ($containerData.labels) {
            $containerLabels = if ($containerData.labels -is [string]) { $containerData.labels | ConvertFrom-Json } else { $containerData.labels }
            
            $this.Labels = $containerLabels

            # Resolve WebUI URL if available
            $webUiLabel = $containerLabels.'net.unraid.docker.webui'
            if ($webUiLabel) {
                $webUiTemplate = $webUiLabel -replace "\[IP\]", $this.IPAddress

                # Replace [PORT:1234] with actual external ports
                # We regex match the token, find the mapping, and swap it
                $portPlaceholderMatch = [regex]::Match($webUiTemplate, '\[PORT:(\d+)\]')
                if ($portPlaceholderMatch.Success) {
                    $internalPort = $portPlaceholderMatch.Groups[1].Value
                    $mappedExternalPort = $internalPort
                    
                    if ($containerData.ports) {
                        $portMapping = $containerData.ports | Where-Object { 
                            "$($_.privatePort)" -eq "$internalPort" 
                        } | Select-Object -First 1
                        
                        if ($portMapping -and $portMapping.publicPort) {
                            $mappedExternalPort = $portMapping.publicPort
                        }
                    }
                    
                    $webUiTemplate = $webUiTemplate -replace "\[PORT:$internalPort\]", $mappedExternalPort
                }

                $this.WebUI = $webUiTemplate
            }
        }

        if ($containerData.ports) {
            $activePorts = $containerData.ports | Where-Object { $_.publicPort }
            
            $portStrings = $activePorts | ForEach-Object {
                "$($_.publicPort):$($_.privatePort)/$($_.type)"
            }
            $this.PortSummary = $portStrings -join ", "
        }

        if ($containerData.mounts) {
            $this.VolumeMappings = foreach ($mountPoint in $containerData.mounts) {
                $sourcePath = if ($mountPoint.Source) { $mountPoint.Source } else { $mountPoint.source }
                $targetPath = if ($mountPoint.Destination) { $mountPoint.Destination } else { $mountPoint.destination }
                
                $accessMode = if ($mountPoint.RW -or $mountPoint.rw -or ($mountPoint.ReadOnly -eq $false)) { "rw" } else { "ro" }

                if ($sourcePath) { 
                    "'$sourcePath' -> '$targetPath' ($accessMode)" 
                }
            }
        }
    }
}

class UnraidServer {
    [string]$Name
    [string]$Description
    [string]$Version
    [string]$Kernel
    [string]$IPAddress
    [string]$WanIP
    [string]$TimeZone
    [string]$Uptime
    [string]$State
    [string]$Motherboard
    [string]$CPU
    [string]$CpuUsage
    [string]$RamUsage
    [string]$ArrayUsage
    [string]$Guid
    [object[]]$NetworkDevices
    [object]$Baseboard
    [object]$SystemInfo
    [object[]]$MemoryLayout
    [string]$Workgroup
    [string]$Domain

    UnraidServer($serverData) {
        $serverInfo = $serverData.server
        $sysInfo = $serverData.info.system
        $cpuInfo = $serverData.info.cpu
        $memoryMetrics = $serverData.metrics.memory

        $this.Name = $serverInfo.name
        $this.IPAddress = $serverInfo.lanip
        $this.Guid = $serverInfo.guid
        $this.Description = $serverData.vars.comment
        $this.State = $serverData.array.state

        if ($serverInfo.wanip) { $this.WanIP = $serverInfo.wanip }
        if ($serverData.vars.timeZone) { $this.TimeZone = $serverData.vars.timeZone }
        if ($serverData.info.versions.core.unraid) { $this.Version = $serverData.info.versions.core.unraid }
        else { $this.Version = "Unknown" }

        if ($serverData.info.versions.core.kernel) { $this.Kernel = $serverData.info.versions.core.kernel }

        $uptimeRawString = $serverData.info.os.uptime
        $this.Uptime = $uptimeRawString -replace "up ", ""
        
        $this.Motherboard = "$($sysInfo.manufacturer) - $($sysInfo.model)"
        
        if ($cpuInfo.threads -and $cpuInfo.threads -ne $cpuInfo.cores) {
            $this.CPU = "$($cpuInfo.brand) ($($cpuInfo.cores) Cores / $($cpuInfo.threads) Threads)"
        }
        else {
            $this.CPU = "$($cpuInfo.brand) ($($cpuInfo.cores) Cores)"
        }
        
        $cpuTotalPercent = $serverData.metrics.cpu.percentTotal
        $this.CpuUsage = "$([math]::Round($cpuTotalPercent, 1))%"

        $ramUsedFormatted = [UnraidSizeFix]::FormatBytes($memoryMetrics.used)
        $ramTotalFormatted = [UnraidSizeFix]::FormatBytes($memoryMetrics.total)
        $ramPercent = [math]::Round($memoryMetrics.percentTotal, 1)
        $this.RamUsage = "$ramPercent% ($ramUsedFormatted / $ramTotalFormatted)"

        if ($serverData.array.capacity.kilobytes.total -gt 0) {
            $arrayUsedBytes = [double]$serverData.array.capacity.kilobytes.used * 1KB
            $arrayTotalBytes = [double]$serverData.array.capacity.kilobytes.total * 1KB
            
            $usagePercent = ($arrayUsedBytes / $arrayTotalBytes) * 100
            $usagePercentRounded = [math]::Round($usagePercent, 1)
            
            $usedString = [UnraidSizeFix]::FormatBytes($arrayUsedBytes)
            $totalString = [UnraidSizeFix]::FormatBytes($arrayTotalBytes)
            
            $this.ArrayUsage = "$usagePercentRounded% ($usedString / $totalString)"
        }
        else {
            $this.ArrayUsage = "N/A"
        }
        
        if ($serverData.info.devices.network) { $this.NetworkDevices = $serverData.info.devices.network }
        if ($serverData.info.baseboard) { $this.Baseboard = $serverData.info.baseboard }
        if ($serverData.info.system) { $this.SystemInfo = $serverData.info.system }
        if ($serverData.info.memory.layout) { $this.MemoryLayout = $serverData.info.memory.layout }
        if ($serverData.vars.workgroup) { $this.Workgroup = $serverData.vars.workgroup }
        if ($serverData.vars.domain) { $this.Domain = $serverData.vars.domain }
    }
}

class UnraidMetrics {
    [double]$CpuTotal
    [object]$CpuCores
    [double]$MemTotal
    [double]$MemUsed
    [double]$MemFree
    [double]$MemBuffCache
    [double]$MemActive
    [double]$MemAvailable
    [string]$MemPercent
    [double]$SwapTotal
    [double]$SwapUsed
    [double]$SwapFree
    [string]$SwapPercent
    [double]$SwapPercentTotal

    UnraidMetrics($metricsData) {
        $this.CpuTotal = [math]::Round($metricsData.metrics.cpu.percentTotal, 1)

        $memoryInfo = $metricsData.metrics.memory
        
        $this.MemTotal     = $memoryInfo.total
        $this.MemUsed      = $memoryInfo.used
        $this.MemFree      = $memoryInfo.free
        $this.MemBuffCache = $memoryInfo.buffcache
        $this.MemActive    = $memoryInfo.active
        $memoryPercent     = [math]::Round($memoryInfo.percentTotal, 1)
        $this.MemPercent   = "$memoryPercent%"

        $this.SwapTotal = $memoryInfo.swapTotal
        $this.SwapUsed  = $memoryInfo.swapUsed
        $this.SwapFree  = $memoryInfo.swapFree

        if ($memoryInfo.percentSwapTotal) {
            $this.SwapPercentTotal = $memoryInfo.percentSwapTotal
            $this.SwapPercent = "$([math]::Round($memoryInfo.percentSwapTotal, 1))%"
        }
        elseif ($this.SwapTotal -gt 0) {
            $swapPercentCalculated = [math]::Round((($this.SwapUsed / $this.SwapTotal) * 100), 1)
            $this.SwapPercent = "$swapPercentCalculated%"
            $this.SwapPercentTotal = $swapPercentCalculated
        }
        else {
            $this.SwapPercent = "0%"
            $this.SwapPercentTotal = 0
        }
        
        if ($metricsData.metrics.cpu.cpus) { $this.CpuCores = $metricsData.metrics.cpu.cpus }
        if ($memoryInfo.available) { $this.MemAvailable = $memoryInfo.available }
    }

    [string] FormatBytes([double]$bytesToFormat) {
        return [UnraidSizeFix]::FormatBytes($bytesToFormat)
    }
}

class UnraidVm {
    [string]$Id
    [string]$Name
    [string]$State

    UnraidVm($vmData) {
        $this.Id = $vmData.id
        $this.Name = $vmData.name
        $this.State = $vmData.state
    }
}

class UnraidNotification {
    [string]$Id
    [string]$Title
    [string]$Subject
    [string]$Description
    [string]$Importance
    [string]$Type
    [string]$Timestamp
    [string]$FormattedTimestamp
    [string]$Link

    UnraidNotification($notificationData) {
        $this.Id = $notificationData.id
        $this.Title = $notificationData.title
        $this.Subject = $notificationData.subject
        $this.Description = $notificationData.description
        $this.Importance = $notificationData.importance
        $this.Type = $notificationData.type
        $this.Link = $notificationData.link
        $this.Timestamp = $notificationData.timestamp
        $this.FormattedTimestamp = $notificationData.formattedTimestamp
    }
}

class UnraidShare {
    [string]$Name
    [string]$Comment
    [string]$Free
    [string]$Used
    [string]$Size
    [string]$Allocator
    [string]$SplitLevel
    [string]$Floor
    [bool]$Cache
    [string[]]$Include
    [string[]]$Exclude
    [string]$Cow
    [string]$LuksStatus

    UnraidShare($shareData) {
        $this.Name = $shareData.name
        $this.Comment = $shareData.comment

        $this.Free = [UnraidSizeFix]::FormatBytes($shareData.free)
        $this.Used = [UnraidSizeFix]::FormatBytes($shareData.used)
        
        $calculatedSize = $shareData.size
        if (! $calculatedSize -or $calculatedSize -eq 0) {
            $calculatedSize = $shareData.used + $shareData.free
        }
        $this.Size = [UnraidSizeFix]::FormatBytes($calculatedSize)
        
        if ($shareData.allocator) { $this.Allocator = $shareData.allocator }
        if ($shareData.splitLevel) { $this.SplitLevel = $shareData.splitLevel }
        if ($shareData.floor) { $this.Floor = $shareData.floor }
        if ($null -ne $shareData.cache) { $this.Cache = [bool]$shareData.cache }
        if ($shareData.include) { $this.Include = $shareData.include }
        if ($shareData.exclude) { $this.Exclude = $shareData.exclude }
        if ($shareData.cow) { $this.Cow = $shareData.cow }
        if ($shareData.luksStatus) { $this.LuksStatus = $shareData.luksStatus }
    }
}

class UnraidPlugin {
    [string]$Name
    [string]$Version
    [bool]$HasApi
    [bool]$HasCli

    UnraidPlugin($pluginData) {
        $this.Name = $pluginData.name
        $this.Version = $pluginData.version
        $this.HasApi = $pluginData.hasApiModule
        $this.HasCli = $pluginData.hasCliModule
    }
}

class UnraidParityHistory {
    [string]$Status
    [int]$Progress
    [string]$Speed
    [int]$Errors
    [int]$Correcting
    [datetime]$Date
    [int]$DurationSeconds
    [string]$Duration

    UnraidParityHistory($parityHistoryData) {
        $this.Status = $parityHistoryData.status
        $this.Progress = $parityHistoryData.progress
        $this.Errors = $parityHistoryData.errors
        $this.Correcting = $parityHistoryData.correcting
        $this.Date = $parityHistoryData.date
        $this.DurationSeconds = $parityHistoryData.duration

        if ($parityHistoryData.speed -gt 0) {
            $this.Speed = [UnraidSizeFix]::FormatBytes($parityHistoryData.speed) + "/s"
        }

        if ($parityHistoryData.duration) {
            # Standard HH:mm:ss calc
            $durationHours = [int][math]::Floor($parityHistoryData.duration / 3600)
            $durationMinutes = [int][math]::Floor(($parityHistoryData.duration % 3600) / 60)
            $durationSecondsLocal = [int]($parityHistoryData.duration % 60)
            
            $hoursFormatted = $durationHours.ToString('D2')
            $minutesFormatted = $durationMinutes.ToString('D2')
            $secondsFormatted = $durationSecondsLocal.ToString('D2')

            $this.Duration = "$hoursFormatted`:$minutesFormatted`:$secondsFormatted"
        }
    }
}

class UnraidUps {
    [string]$Name
    [string]$Model
    [string]$Status
    [int]$BatteryCharge
    [string]$BatteryRuntime
    [int]$Load
    [double]$InputVoltage
    [double]$OutputVoltage

    UnraidUps($upsData) {
        $this.Name = $upsData.name
        $this.Model = $upsData.model
        $this.Status = $upsData.status
        
        if ($upsData.battery) {
            $this.BatteryCharge = $upsData.battery.chargeLevel
            $this.BatteryRuntime = $upsData.battery.estimatedRuntime
        }
        
        if ($upsData.power) {
            $this.Load = $upsData.power.loadPercentage
            $this.InputVoltage = $upsData.power.inputVoltage
            $this.OutputVoltage = $upsData.power.outputVoltage
        }
    }
}