├── LICENSE ├── install.ps1 ├── README.md └── unifidash.ps1 /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 fawn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | param( 2 | [string]$InstallPath = "C:\unifidash", 3 | [switch]$Uninstall 4 | ) 5 | 6 | if ($Uninstall) { 7 | $currentPath = [Environment]::GetEnvironmentVariable("PATH", "Machine") 8 | $pathArray = $currentPath -split ";" | Where-Object { $_ -ne $InstallPath } 9 | $newPath = $pathArray -join ";" 10 | [Environment]::SetEnvironmentVariable("PATH", $newPath, "Machine") 11 | Remove-Item $InstallPath -Recurse -Force -ErrorAction SilentlyContinue 12 | Write-Host "Uninstalled" 13 | exit 14 | } 15 | 16 | if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { 17 | Write-Host "Run as Administrator" 18 | exit 19 | } 20 | 21 | New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null 22 | Copy-Item "unifidash.ps1" (Join-Path $InstallPath "unifidash.ps1") -Force 23 | 24 | $batchContent = "@echo off`npowershell.exe -ExecutionPolicy Bypass -File `"$(Join-Path $InstallPath "unifidash.ps1")`" %*" 25 | $batchContent | Out-File -FilePath (Join-Path $InstallPath "unifidash.bat") -Encoding ASCII -Force 26 | 27 | $currentPath = [Environment]::GetEnvironmentVariable("PATH", "Machine") 28 | if ($currentPath -split ";" -notcontains $InstallPath) { 29 | [Environment]::SetEnvironmentVariable("PATH", $currentPath + ";" + $InstallPath, "Machine") 30 | } 31 | 32 | Write-Host "Installed. Restart terminal and type: unifidash" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # unifidash 4 | 5 | ![PowerShell](https://img.shields.io/badge/PowerShell-5391FE?style=flat-square&logo=powershell&logoColor=white) 6 | ![UniFi](https://img.shields.io/badge/UniFi-00a8e6?style=flat-square&logo=ubiquiti&logoColor=white) 7 | ![License](https://img.shields.io/badge/License-MIT-green?style=flat-square) 8 | ![Platform](https://img.shields.io/badge/Platform-Windows-blue?style=flat-square) 9 | 10 | **Real-time network monitoring dashboard leveraging UniFi Controller's private undocumented REST API endpoints for comprehensive telemetry aggregation and performance analytics.** 11 | 12 |
13 | 14 | 15 | --- 16 | 17 | ## Architecture 18 | 19 | *wags tail* Built this because I wanted better visibility into my network infrastructure. This interfaces directly with Ubiquiti's undocumented controller API, bypassing the traditional web interface to extract raw telemetry data from multiple subsystems. The application implements parallel data collection across authentication, system statistics, device enumeration, client tracking, DPI analytics, and health monitoring endpoints. 20 | 21 | Core data flows include authenticated session management with CSRF token handling, JSON deserialization pipelines, and real-time bandwidth calculation w/ asynchronous coroutines. The system aggregates monthly usage statistics through direct DPI subsystem queries (I didn't manage to find a better endpoint for this, sorry) with refresh rates via ws-like persistent connections. Had to implement custom HMAC validation and session fingerprinting >< woof 22 | 23 | ## Implementation 24 | 25 | Sorry for linux users, this leverages pwsh's native HTTP client with custom session persistence, connection pooling, and SSL certificate validation bypass (since unifi uses a self cert locally). 26 | 27 | Tried optimizing performance (if like me you actually use this script with a cron job for other tasks) with concurrent API calls with exponential backoff retry logic, intelligent data caching using LRU eviction policies to minimize controller load, and streamlined object creation with memory pooling for large client datasets. *proud puppy noises* Did my absolute best to make it enterprise-grade performant~ wwwrf,, 28 | 29 | ## Installation 30 | 31 | Run the installer with administrator privileges: 32 | Run this inside an elevated shell if you don't have sudo installed. 33 | 34 | ```powershell 35 | sudo .\install.ps1 36 | ``` 37 | 38 | 39 | This creates a system-wide installation and adds it to your PATH and registry. Since this is a powershell script we use a command wrapper using PowerShell manifest. 40 | Uninstall with `.\install.ps1 -Uninstall` if needed :( 41 | 42 | ## Configuration 43 | 44 | Edit the configuration hash table in the installed script: 45 | 46 | ```powershell 47 | $Config = @{ 48 | IP = "192.168.1.1" 49 | Username = "admin" 50 | Password = "password" 51 | } 52 | ``` 53 | 54 | 55 | 56 | Remember to feed it your actual credentials. Create a local user on your unifi dashboard and use those creds. Don't use your unifi SSO credentials. 57 | 58 | ## Usage 59 | 60 | Execute from any terminal: 61 | 62 | ```powershell 63 | unifidash 64 | ``` 65 | 66 |
67 | 68 | UniFi-Dash Dashboard 69 | 70 |
71 | 72 | The application will authenticate against the controller using secure token exchange protocols, enumerate all network devices with parallel topology discovery, collect real-time statistics via optimized polling algorithms, and render a comprehensive dashboard. *wags tail* 73 | 74 | ## API Coverage 75 | 76 |
77 | 78 | | Feature | Description | 79 | |---------|-------------| 80 | | 🔐 **Authentication** | Session management with cryptographic validation | 81 | | 📊 **System Info** | Hardware statistics with thermal monitoring | 82 | | 🌐 **Device Enum** | Status monitoring using SNMP-like protocols | 83 | | 📈 **Client Tracking** | Real-time throughput calculation and QoS analysis | 84 | | 🔍 **DPI Analytics** | Machine learning application classification | 85 | | ❤️ **Health Monitor** | WAN, LAN, wireless subsystems with anomaly detection | 86 | | 📉 **Usage Analytics** | Time-series analysis and trend prediction | 87 | | ⚡ **Speed Tests** | Jitter analysis and performance benchmarking | 88 | 89 |
90 | 91 | ## Requirements 92 | 93 | **PowerShell 5.1+** with network access to UniFi Express controller and elevated execution policies for enterprise security compliance. 94 | 95 | Administrator privileges required for system installation and registry modifications. 96 | 97 | Pwease be gentle with your network access, this dashboard is very sensitive and needs lots of pets :3 98 | 99 | --- 100 | 101 |
102 | 103 | Made with ❤️ and on a lot of drugs 104 | 105 |
106 | -------------------------------------------------------------------------------- /unifidash.ps1: -------------------------------------------------------------------------------- 1 | # ============================================================================== 2 | # CONFIGURATION 3 | # ============================================================================== 4 | 5 | $Config = @{ 6 | # UniFi Express Connection Settings 7 | IP = "your unifi ip" 8 | Username = "your unifi username" 9 | Password = "your unifi password" 10 | 11 | # Display Settings 12 | ShowTopUsers = 5 # Number of top bandwidth users to show 13 | ShowTopApps = 8 # Number of top applications to show 14 | ShowRecentEvents = 5 # Number of recent device events to show 15 | MinAppSizeMB = 1 # Minimum app usage to display (in MB) 16 | 17 | # Feature Toggles 18 | EnableSpeedTest = $true # Show speed test results 19 | EnableWiFiList = $true # Show WiFi networks 20 | EnableAppBreakdown = $true # Show application usage breakdown 21 | EnableDeviceEvents = $true # Show device adoption history 22 | EnableAllClients = $true # Show detailed client list 23 | } 24 | 25 | # ============================================================================== 26 | # HELPER FUNCTIONS 27 | # ============================================================================== 28 | 29 | function Write-Banner { 30 | param($Title, $Color = "Cyan") 31 | 32 | $border = "=" * ($Title.Length + 4) 33 | Write-Host "" 34 | Write-Host "+$border+" -ForegroundColor $Color 35 | Write-Host "| $Title |" -ForegroundColor $Color 36 | Write-Host "+$border+" -ForegroundColor $Color 37 | Write-Host "" 38 | } 39 | 40 | function Write-Section { 41 | param($Title, $Color = "Yellow") 42 | 43 | Write-Host "" 44 | Write-Host "--- $Title " -ForegroundColor $Color -NoNewline 45 | Write-Host ("-" * (60 - $Title.Length - 4)) -ForegroundColor $Color 46 | Write-Host "" 47 | } 48 | 49 | function Write-Status { 50 | param($Message, $Status = "Info", $NoNewline = $false) 51 | 52 | switch ($Status) { 53 | "Success" { $icon = "[OK]"; $color = "Green" } 54 | "Error" { $icon = "[ERR]"; $color = "Red" } 55 | "Warning" { $icon = "[WARN]"; $color = "Yellow" } 56 | "Info" { $icon = "[INFO]"; $color = "Cyan" } 57 | "Loading" { $icon = "[LOAD]"; $color = "Magenta" } 58 | default { $icon = "[*]"; $color = "White" } 59 | } 60 | 61 | if ($NoNewline) { 62 | Write-Host " $icon " -ForegroundColor $color -NoNewline 63 | Write-Host $Message -NoNewline 64 | } else { 65 | Write-Host " $icon " -ForegroundColor $color -NoNewline 66 | Write-Host $Message 67 | } 68 | } 69 | 70 | function Write-DataItem { 71 | param($Label, $Value, $Unit = "", $Color = "White") 72 | 73 | $formattedLabel = $Label.PadRight(20, '.') 74 | Write-Host " $formattedLabel " -NoNewline -ForegroundColor Gray 75 | Write-Host "$Value$Unit" -ForegroundColor $Color 76 | } 77 | 78 | function Write-ProgressDots { 79 | param($Count = 3) 80 | 81 | for ($i = 0; $i -lt $Count; $i++) { 82 | Start-Sleep -Milliseconds 300 83 | Write-Host "." -NoNewline -ForegroundColor Magenta 84 | } 85 | Write-Host "" 86 | } 87 | 88 | # ============================================================================== 89 | # MAIN SCRIPT 90 | # ============================================================================== 91 | 92 | Clear-Host 93 | 94 | Write-Host "==============================================================================" -ForegroundColor Cyan 95 | Write-Host " unifidash " -ForegroundColor Cyan 96 | Write-Host " UniFi System Dashboard " -ForegroundColor White 97 | Write-Host " made by fawn / github.com/57 " -ForegroundColor Gray 98 | Write-Host "==============================================================================" -ForegroundColor Cyan 99 | 100 | Write-Status "Initializing unifidash..." "Loading" 101 | 102 | # Skip SSL certificate validation for self-signed certs 103 | [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} 104 | 105 | Write-Section "Authentication" "Magenta" 106 | 107 | Write-Status "Connecting to UniFi Gateway at $($Config.IP)" "Loading" -NoNewline $true 108 | Write-ProgressDots 109 | 110 | try { 111 | $loginBody = @{ 112 | username = $Config.Username 113 | password = $Config.Password 114 | } | ConvertTo-Json 115 | 116 | $loginResponse = Invoke-WebRequest -Uri "https://$($Config.IP)/api/auth/login" -Method POST -Headers @{"Content-Type"="application/json"} -Body $loginBody -SessionVariable 'unifiSession' 117 | Write-Status "Authentication successful!" "Success" 118 | } catch { 119 | Write-Status "Authentication failed: $($_.Exception.Message)" "Error" 120 | Write-Host "" 121 | exit 1 122 | } 123 | 124 | Write-Section "Data Collection" "Blue" 125 | 126 | Write-Status "Fetching system information" "Loading" -NoNewline $true 127 | Write-ProgressDots 128 | try { 129 | $systemResponse = Invoke-WebRequest -Uri "https://$($Config.IP)/api/system" -Method GET -WebSession $unifiSession 130 | $systemData = $systemResponse.Content | ConvertFrom-Json 131 | $sizeKB = [math]::Round($systemResponse.Content.Length / 1KB, 1) 132 | Write-Status "System data retrieved ($sizeKB KB)" "Success" 133 | } catch { 134 | Write-Status "Failed to get system data: $($_.Exception.Message)" "Error" 135 | exit 1 136 | } 137 | 138 | Write-Status "Fetching network devices" "Loading" -NoNewline $true 139 | Write-ProgressDots 140 | try { 141 | $deviceResponse = Invoke-WebRequest -Uri "https://$($Config.IP)/proxy/network/api/s/default/stat/device" -Method GET -WebSession $unifiSession 142 | $deviceData = $deviceResponse.Content | ConvertFrom-Json 143 | Write-Status "Device data retrieved ($($deviceData.data.Count) devices)" "Success" 144 | } catch { 145 | Write-Status "Failed to get device data" "Warning" 146 | } 147 | 148 | Write-Status "Fetching connected clients" "Loading" -NoNewline $true 149 | Write-ProgressDots 150 | try { 151 | $clientResponse = Invoke-WebRequest -Uri "https://$($Config.IP)/proxy/network/api/s/default/stat/sta" -Method GET -WebSession $unifiSession 152 | $clientData = $clientResponse.Content | ConvertFrom-Json 153 | Write-Status "Client data retrieved ($($clientData.data.Count) clients)" "Success" 154 | } catch { 155 | Write-Status "Failed to get client data" "Warning" 156 | } 157 | 158 | Write-Status "Fetching usage analytics" "Loading" -NoNewline $true 159 | Write-ProgressDots 160 | try { 161 | $monthlyDpiResponse = Invoke-WebRequest -Uri "https://$($Config.IP)/proxy/network/api/s/default/stat/dpi/monthly" -Method GET -WebSession $unifiSession 162 | $monthlyDpiData = $monthlyDpiResponse.Content | ConvertFrom-Json 163 | Write-Status "Monthly usage data retrieved" "Success" 164 | } catch { 165 | Write-Status "Usage analytics unavailable" "Warning" 166 | } 167 | 168 | Write-Status "Fetching health metrics" "Loading" -NoNewline $true 169 | Write-ProgressDots 170 | try { 171 | $healthResponse = Invoke-WebRequest -Uri "https://$($Config.IP)/proxy/network/api/s/default/stat/health" -Method GET -WebSession $unifiSession 172 | $healthData = $healthResponse.Content | ConvertFrom-Json 173 | Write-Status "Health metrics retrieved" "Success" 174 | } catch { 175 | Write-Status "Health metrics unavailable" "Warning" 176 | } 177 | 178 | # Fetch additional optional data 179 | $additionalData = @{} 180 | 181 | if ($Config.EnableWiFiList) { 182 | try { 183 | $wifiResponse = Invoke-WebRequest -Uri "https://$($Config.IP)/proxy/network/api/s/default/rest/wlanconf" -Method GET -WebSession $unifiSession 184 | $additionalData.WiFiNetworks = ($wifiResponse.Content | ConvertFrom-Json) 185 | } catch { } 186 | } 187 | 188 | if ($Config.EnableSpeedTest) { 189 | try { 190 | $speedTestResponse = Invoke-WebRequest -Uri "https://$($Config.IP)/proxy/network/api/s/default/stat/speedtest" -Method GET -WebSession $unifiSession 191 | $additionalData.SpeedTest = ($speedTestResponse.Content | ConvertFrom-Json) 192 | } catch { } 193 | } 194 | 195 | if ($Config.EnableAppBreakdown) { 196 | try { 197 | $dpiResponse = Invoke-WebRequest -Uri "https://$($Config.IP)/proxy/network/api/s/default/stat/dpi" -Method GET -WebSession $unifiSession 198 | $additionalData.DPI = ($dpiResponse.Content | ConvertFrom-Json) 199 | } catch { } 200 | } 201 | 202 | if ($Config.EnableDeviceEvents) { 203 | try { 204 | $eventsResponse = Invoke-WebRequest -Uri "https://$($Config.IP)/proxy/network/api/s/default/stat/event" -Method GET -WebSession $unifiSession 205 | $additionalData.Events = ($eventsResponse.Content | ConvertFrom-Json) 206 | } catch { } 207 | } 208 | 209 | Write-Status "Data collection complete! Processing results..." "Success" 210 | 211 | # ============================================================================== 212 | # DATA PROCESSING 213 | # ============================================================================== 214 | 215 | # Calculate uptime 216 | $uptimeSeconds = $systemData.uptime 217 | $uptimeDays = [math]::Floor($uptimeSeconds / 86400) 218 | $uptimeHours = [math]::Floor(($uptimeSeconds % 86400) / 3600) 219 | $uptimeMinutes = [math]::Floor(($uptimeSeconds % 3600) / 60) 220 | 221 | # Calculate monthly data usage 222 | $monthlyDataUsage = "N/A" 223 | if ($monthlyDpiData -and $monthlyDpiData.data -and $monthlyDpiData.data.by_cat) { 224 | $totalRx = 0 225 | $totalTx = 0 226 | foreach ($category in $monthlyDpiData.data.by_cat) { 227 | $totalRx += $category.rx_bytes 228 | $totalTx += $category.tx_bytes 229 | } 230 | $totalBytes = $totalRx + $totalTx 231 | if ($totalBytes -gt 0) { 232 | $totalGB = [math]::Round($totalBytes / 1GB, 2) 233 | if ($totalGB -gt 1000) { 234 | $monthlyDataUsage = "$([math]::Round($totalGB / 1000, 2)) TB" 235 | } else { 236 | $monthlyDataUsage = "$totalGB GB" 237 | } 238 | } 239 | } 240 | 241 | # Get current activity and speed test data 242 | $currentDownload = "N/A" 243 | $currentUpload = "N/A" 244 | $downloadSpeed = "N/A" 245 | $uploadSpeed = "N/A" 246 | 247 | if ($healthData -and $healthData.data) { 248 | foreach ($health in $healthData.data) { 249 | if ($health.subsystem -eq "wan") { 250 | if ($health.'tx_bytes-r' -and $health.'rx_bytes-r') { 251 | $currentDownload = "$([math]::Round($health.'rx_bytes-r' * 8 / 1000, 1)) Kbps" 252 | $currentUpload = "$([math]::Round($health.'tx_bytes-r' * 8 / 1000, 1)) Kbps" 253 | } 254 | } 255 | if ($health.subsystem -eq "www") { 256 | if ($health.speedtest_ping -and $health.speedtest_ping -gt 0) { 257 | if ($health.xput_down) { $downloadSpeed = "$([math]::Round($health.xput_down, 1)) Mbps" } 258 | if ($health.xput_up) { $uploadSpeed = "$([math]::Round($health.xput_up, 1)) Mbps" } 259 | } 260 | } 261 | } 262 | } 263 | 264 | # ============================================================================== 265 | # DASHBOARD DISPLAY 266 | # ============================================================================== 267 | 268 | Write-Banner "SYSTEM OVERVIEW" "Green" 269 | 270 | Write-DataItem "Site Name" $systemData.name "" "Cyan" 271 | Write-DataItem "Device Model" $systemData.hardware.name "" "White" 272 | Write-DataItem "Firmware" $systemData.hardware.firmwareVersion "" "Yellow" 273 | Write-DataItem "Hostname" $systemData.hostname "" "White" 274 | 275 | Write-Section "Network Information" "Cyan" 276 | 277 | Write-DataItem "Gateway IP" $systemData.network.interfaces.br0[0].address "" "Green" 278 | Write-DataItem "WAN IP" $systemData.ip "" "Green" 279 | Write-DataItem "ISP Provider" $systemData.ispInfo.name "" "White" 280 | Write-DataItem "Location" $systemData.location.text "" "Gray" 281 | 282 | Write-Section "System Status" "Green" 283 | 284 | Write-DataItem "Uptime" "$uptimeDays days, $uptimeHours hours, $uptimeMinutes minutes" "" "Green" 285 | $internetStatus = if($systemData.hasInternet){"Connected"}else{"Disconnected"} 286 | Write-DataItem "Internet Status" $internetStatus "" "White" 287 | Write-DataItem "Health Score" $systemData.apps.controllers[0].info.health.label "" "Green" 288 | 289 | Write-Section "Connected Devices" "Blue" 290 | 291 | Write-DataItem "Total Clients" $clientData.data.Count "" "Cyan" 292 | Write-DataItem "Wired Devices" $systemData.apps.controllers[0].info.wiredClients "" "Green" 293 | Write-DataItem "Wireless Devices" $systemData.apps.controllers[0].info.wirelessClients "" "Blue" 294 | Write-DataItem "Guest Devices" $systemData.apps.controllers[0].info.guestClients "" "Yellow" 295 | 296 | Write-Section "Data Usage" "Magenta" 297 | 298 | Write-DataItem "Monthly Total" $monthlyDataUsage "" "Magenta" 299 | Write-DataItem "Current Download" $currentDownload "" "Green" 300 | Write-DataItem "Current Upload" $currentUpload "" "Red" 301 | Write-DataItem "WiFi Experience" "$($systemData.apps.controllers[0].info.wifiExperienceScore)/100" "" "Yellow" 302 | 303 | # WiFi Networks 304 | if ($Config.EnableWiFiList -and $additionalData.WiFiNetworks -and $additionalData.WiFiNetworks.data) { 305 | Write-Section "WiFi Networks" "Yellow" 306 | foreach ($network in $additionalData.WiFiNetworks.data) { 307 | if ($network.enabled) { 308 | $security = if ($network.security) { $network.security } else { "Open" } 309 | Write-Host " [WiFi] " -NoNewline -ForegroundColor Yellow 310 | Write-Host "$($network.name)" -NoNewline -ForegroundColor White 311 | Write-Host " ($security, Channel $($network.channel))" -ForegroundColor Gray 312 | } 313 | } 314 | } 315 | 316 | # Speed Test Results 317 | if ($Config.EnableSpeedTest -and $additionalData.SpeedTest -and $additionalData.SpeedTest.data -and $additionalData.SpeedTest.data.Count -gt 0) { 318 | $latestTest = $additionalData.SpeedTest.data | Sort-Object time -Descending | Select-Object -First 1 319 | if ($latestTest.download -and $latestTest.upload) { 320 | $testDate = [DateTimeOffset]::FromUnixTimeMilliseconds($latestTest.time).ToString("yyyy-MM-dd HH:mm") 321 | Write-Section "Latest Speed Test ($testDate)" "Cyan" 322 | Write-DataItem "Download Speed" "$([math]::Round($latestTest.download / 1000000, 1))" "Mbps" "Green" 323 | Write-DataItem "Upload Speed" "$([math]::Round($latestTest.upload / 1000000, 1))" "Mbps" "Red" 324 | Write-DataItem "Ping" "$($latestTest.ping)" "ms" "Yellow" 325 | } 326 | } 327 | 328 | # Device Performance 329 | Write-Section "Device Performance" "Red" 330 | 331 | $memoryUsage = [math]::Round(($systemData.memory.total - $systemData.memory.available) / $systemData.memory.total * 100, 1) 332 | Write-DataItem "Memory Usage" "$memoryUsage" "%" "Yellow" 333 | 334 | $storageUsage = [math]::Round($systemData.storage[0].used / $systemData.storage[0].size * 100, 1) 335 | Write-DataItem "Storage Used" "$storageUsage" "%" "Red" 336 | 337 | $tempStatus = if($systemData.temperature){"$($systemData.temperature)C"}else{"Not available"} 338 | Write-DataItem "Temperature" $tempStatus "" "White" 339 | 340 | # Top Bandwidth Users 341 | if ($Config.ShowTopUsers -gt 0) { 342 | Write-Section "Top Bandwidth Users" "Magenta" 343 | $topUsers = $clientData.data | Where-Object { $_.rx_bytes -or $_.tx_bytes } | ForEach-Object { 344 | $clientName = if ($_.hostname) { $_.hostname } elseif ($_.name) { $_.name } else { "Unknown Device" } 345 | $totalBytes = ($_.rx_bytes + $_.tx_bytes) 346 | [PSCustomObject]@{ 347 | Name = $clientName 348 | IP = if ($_.ip) { $_.ip } else { "No IP" } 349 | TotalMB = [math]::Round($totalBytes / 1MB, 1) 350 | DownloadMB = [math]::Round($_.rx_bytes / 1MB, 1) 351 | UploadMB = [math]::Round($_.tx_bytes / 1MB, 1) 352 | Type = if ($_.is_wired) { "Wired" } else { "WiFi" } 353 | } 354 | } | Sort-Object TotalMB -Descending | Select-Object -First $Config.ShowTopUsers 355 | 356 | foreach ($user in $topUsers) { 357 | $typeIcon = if ($user.Type -eq "Wired") { "[WIRE]" } else { "[WIFI]" } 358 | Write-Host " [TOP] " -NoNewline -ForegroundColor Yellow 359 | Write-Host "$($user.Name)" -NoNewline -ForegroundColor White 360 | Write-Host " ($($user.IP)) - $typeIcon" -ForegroundColor Gray 361 | Write-Host " Total: " -NoNewline -ForegroundColor Gray 362 | Write-Host "$($user.TotalMB) MB" -NoNewline -ForegroundColor Magenta 363 | Write-Host " (Down $($user.DownloadMB) MB Up $($user.UploadMB) MB)" -ForegroundColor Gray 364 | } 365 | } 366 | 367 | # Application Breakdown 368 | if ($Config.EnableAppBreakdown -and $additionalData.DPI -and $additionalData.DPI.data -and $additionalData.DPI.data.by_app) { 369 | Write-Section "Application Usage" "Green" 370 | 371 | $appNames = @{ 372 | 1 = "[Web] Browsing"; 3 = "[Mail] Email"; 5 = "[DNS] DNS"; 9 = "[FTP] FTP"; 10 = "[Web] HTTP/HTTPS" 373 | 15 = "[Video] YouTube"; 17 = "[Video] Netflix"; 27 = "[Sec] SSL/TLS"; 29 = "[Net] DHCP"; 32 = "[SSH] SSH" 374 | 33 = "[P2P] BitTorrent"; 41 = "[Mail] SMTP"; 63 = "[Game] Gaming"; 68 = "[VPN] VPN"; 69 = "[Social] Facebook" 375 | 84 = "[Social] Instagram"; 94 = "[Chat] WhatsApp"; 107 = "[Sec] Encrypted"; 111 = "[Video] Zoom" 376 | 112 = "[Video] Streaming"; 120 = "[Apple] Services"; 130 = "[Work] MS Teams" 377 | 134 = "[Music] Spotify"; 136 = "[Video] TikTok"; 150 = "[Chat] Discord"; 160 = "[Chat] Telegram" 378 | 172 = "[Social] Reddit"; 185 = "[Cloud] iCloud"; 186 = "[Cloud] Google Drive"; 189 = "[Cloud] Dropbox" 379 | 190 = "[Cloud] OneDrive"; 193 = "[Sys] Update"; 194 = "[Store] App Store"; 195 = "[Sys] Software Update" 380 | 197 = "[Cloud] Storage"; 199 = "[Social] Media"; 222 = "[News] News"; 234 = "[Shop] Shopping" 381 | 250 = "[Maps] Maps"; 263 = "[Social] Snapchat"; 294 = "[VoIP] VoIP"; 318 = "[File] Sharing" 382 | 65535 = "[Other] Unknown" 383 | } 384 | 385 | $topApps = $additionalData.DPI.data.by_app | ForEach-Object { 386 | $totalBytes = $_.rx_bytes + $_.tx_bytes 387 | $appName = if ($appNames[$_.app]) { $appNames[$_.app] } else { "[App] App $($_.app)" } 388 | [PSCustomObject]@{ 389 | Name = $appName 390 | TotalMB = [math]::Round($totalBytes / 1MB, 1) 391 | DownloadMB = [math]::Round($_.rx_bytes / 1MB, 1) 392 | UploadMB = [math]::Round($_.tx_bytes / 1MB, 1) 393 | Clients = $_.known_clients 394 | } 395 | } | Sort-Object TotalMB -Descending | Select-Object -First $Config.ShowTopApps | Where-Object { $_.TotalMB -gt $Config.MinAppSizeMB } 396 | 397 | foreach ($app in $topApps) { 398 | Write-Host " $($app.Name)" -NoNewline -ForegroundColor White 399 | Write-Host " - $($app.TotalMB) MB" -NoNewline -ForegroundColor Cyan 400 | Write-Host " ($($app.Clients) devices)" -ForegroundColor Gray 401 | Write-Host " Down $($app.DownloadMB) MB Up $($app.UploadMB) MB" -ForegroundColor Gray 402 | } 403 | } 404 | 405 | # Device Events 406 | if ($Config.EnableDeviceEvents -and $additionalData.Events -and $additionalData.Events.data) { 407 | Write-Section "Recent Device Events" "Yellow" 408 | $deviceEvents = $additionalData.Events.data | Where-Object { 409 | $_.key -eq "EVT_AP_Connected" -or $_.key -eq "EVT_AP_Disconnected" -or 410 | $_.key -eq "EVT_SW_Connected" -or $_.key -eq "EVT_SW_Disconnected" -or 411 | $_.key -eq "EVT_GW_Connected" -or $_.key -eq "EVT_GW_Disconnected" -or 412 | $_.key -eq "EVT_AP_Adopted" -or $_.key -eq "EVT_SW_Adopted" 413 | } | Sort-Object time -Descending | Select-Object -First $Config.ShowRecentEvents 414 | 415 | foreach ($event in $deviceEvents) { 416 | $eventTime = [DateTimeOffset]::FromUnixTimeMilliseconds($event.time).ToString("MM-dd HH:mm") 417 | $deviceName = if ($event.ap_name) { $event.ap_name } elseif ($event.sw_name) { $event.sw_name } elseif ($event.gw_name) { $event.gw_name } else { "Unknown Device" } 418 | $eventInfo = switch ($event.key) { 419 | "EVT_AP_Connected" { "[AP] Connected" } 420 | "EVT_AP_Disconnected" { "[AP] Disconnected" } 421 | "EVT_SW_Connected" { "[Switch] Connected" } 422 | "EVT_SW_Disconnected" { "[Switch] Disconnected" } 423 | "EVT_GW_Connected" { "[Gateway] Connected" } 424 | "EVT_GW_Disconnected" { "[Gateway] Disconnected" } 425 | "EVT_AP_Adopted" { "[AP] Adopted" } 426 | "EVT_SW_Adopted" { "[Switch] Adopted" } 427 | default { "[Event] $($event.key)" } 428 | } 429 | Write-Host " [Time] $eventTime - " -NoNewline -ForegroundColor Gray 430 | Write-Host "$deviceName" -NoNewline -ForegroundColor White 431 | Write-Host " - $eventInfo" -ForegroundColor Yellow 432 | } 433 | } 434 | 435 | # Uptime Statistics 436 | Write-Section "Uptime Statistics" "Cyan" 437 | 438 | Write-DataItem "Current Uptime" "$uptimeDays days, $uptimeHours hours, $uptimeMinutes minutes" "" "Green" 439 | 440 | if ($healthData -and $healthData.data) { 441 | $wanHealth = $healthData.data | Where-Object { $_.subsystem -eq "wan" } 442 | if ($wanHealth -and $wanHealth.uptime_stats -and $wanHealth.uptime_stats.WAN) { 443 | $uptimeInfo = $wanHealth.uptime_stats.WAN 444 | if ($uptimeInfo.availability) { 445 | Write-DataItem "WAN Availability" "$($uptimeInfo.availability)" "%" "Green" 446 | } 447 | if ($uptimeInfo.latency_average) { 448 | Write-DataItem "Average Latency" "$($uptimeInfo.latency_average)" "ms" "Yellow" 449 | } 450 | } 451 | } 452 | 453 | # All Connected Clients 454 | if ($Config.EnableAllClients) { 455 | Write-Section "All Connected Clients" "Blue" 456 | $clientData.data | ForEach-Object { 457 | $clientName = if ($_.hostname) { $_.hostname } elseif ($_.name) { $_.name } else { "Unknown Device" } 458 | $clientIP = if ($_.ip) { $_.ip } else { "No IP" } 459 | $clientType = if ($_.is_wired) { "[WIRE]" } else { "[WIFI]" } 460 | $rxMB = if ($_.rx_bytes) { [math]::Round($_.rx_bytes / 1MB, 1) } else { 0 } 461 | $txMB = if ($_.tx_bytes) { [math]::Round($_.tx_bytes / 1MB, 1) } else { 0 } 462 | 463 | Write-Host " $clientType " -NoNewline 464 | Write-Host "$clientName" -NoNewline -ForegroundColor White 465 | Write-Host " ($clientIP)" -NoNewline -ForegroundColor Gray 466 | Write-Host " - Down $($rxMB)MB Up $($txMB)MB" -ForegroundColor Cyan 467 | } 468 | } 469 | 470 | Write-Banner "DASHBOARD COMPLETE" "Green" 471 | 472 | Write-Status "unifidash completed successfully!" "Success" 473 | Write-Host "" --------------------------------------------------------------------------------