What components would you use to display the information in this complex data structure?

I have gotten some data to display with various controls as I have played around with PoshUD. I am looking for suggestions to extracting the data from a complex data structure.

Here’s the data structure I have:

licenseObj @{ 
    qtmodeler =
        [PSCustomObject]@{
            FullName = "QT Modeler"
            DaemonName = "aqtimod"
            DaemonStatus = "UP"
            DaemonVersion = "v11.8"
            LicenseServer = "27001@server.dom"
            LicenseFile = "C:\Program Files\QTModeler _802_ux64\flex _ win_x64\license _Oct_2013.lic"
            Features = "{Feature1, Feature2}"
            Feature1 @{
                LicensesUsed = "2"
                LicensesTotal = "7"
                LicenseUsers = @(
                    "User1 started at TIMESTAMP1"
                    "User2 started at TIMESTAMP2"
                )
            }
            Feature2 @{
              LicensesUsed = "0"
              LicensesTotal = "15"
              LicenseUsers = @()
           }
        }
}

This is just one entry in the structure as I have built it but it gives all of the parts for the “QT Modeler” license service; the actual object has 4 different license services within at runtime.

You may notice that “LicenseUsers” could be 0 to N depending on the number of available licenses and users using it. Also, the number of “Features” could be 1 to N.

At the end of this I am trying to get a simple dashboard (I was thinking cards or a collapsible) with a basic set of information (what’s the service? is it up? how many licenses are there and how many consumed?) and then another display (page?) with the full details for a particular service. Counters with history or graphs would be interesting but unnecessary.

I tried just using cards to make a bunch of text appear but I seem to be missing something in Dashboard because what works on the commandline doesn’t seem to give me data back within the PoshUD. Admittedly, I am still struggling to figure out how to co-mingle powershell commandlets and PoshUD elements (foreach loops, for example) and I might have missed a few obvious things.

Does anyone have thoughts on how to tackle this?

Any help is very appreciated. Cheers!

It is not pretty but ih hope this will give you a few components to start:

Get-UDDashboard | Stop-UDDashboard

$pages = @()
$pages += New-UDPage -Name 'main' -Content {
    
    New-UDRow -Endpoint {
        $data = 1..10 | foreach{ 
            [PSCustomObject]@{
                FullName = (Get-Process).Name | Get-Random #"QT Modeler"
                DaemonName = (Get-Service).Name | Get-Random #"aqtimod"
                DaemonStatus = ('UP','Down' | Get-Random)
                DaemonVersion = "v11.8"
                LicenseServer = "27001@server.dom"
                LicenseFile = "C:\Program Files\QTModeler _802_ux64\flex _ win_x64\license _Oct_2013.lic"
                Features = @('Feature1', 'Feature2')
                Feature1 = @{
                    LicensesUsed = Get-Random -Minimum 0 -Maximum 20
                    LicensesTotal = Get-Random -Minimum 20 -Maximum 30
                    LicenseUsers = @(
                        "User1 started at TIMESTAMP1"
                        "User2 started at TIMESTAMP2"
                    )
                }
                Feature2 = @{
                  LicensesUsed = Get-Random -Minimum 0 -Maximum 20
                  LicensesTotal = Get-Random -Minimum 20 -Maximum 30
                  LicenseUsers = @()
               }
            }
        }
        
        foreach($dataset in $data){
            New-UDColumn -Size 2 -Content {
                $Color = 'Green'
                if($dataset.DaemonStatus -ne 'UP'){$Color = 'Red'}

                New-UDCard -Title "$($dataset.FullName) - $($dataset.DaemonName)" -Content {
                    New-UDRow {$dataset.DaemonStatus}
                    New-UDRow {$dataset.DaemonVersion}
                    New-UDRow {$dataset.LicenseServer}
                    
                } -Reveal {
                    foreach($feature in $dataset.Features) {
                         New-UDHeading -Text "Name: $feature" -Size 5
                         New-UDRow {"LicensesUsed: $($dataset.$feature.LicensesUsed)"}
                         New-UDRow {"LicensesTotal: $($dataset.$feature.LicensesTotal)"}
                    }
                } -BackgroundColor $Color
            }
        }
    }
}


$dashboard = New-UDDashboard -Title "main" -Pages $pages

Start-UDDashboard -Port 20000 -Dashboard $dashboard

@psott - Thanks so much for the help. I got pretty far today and things are looking pretty good. And thanks to those who reached out to chat too!

If you want to see it in action: http://cfans-rsl-02.oit.umn.edu (note: There is a fake service defined to demo the color change).

The question I have is: how do I schedule this thing so that the data that I am collecting from my custom function is being updated on a regular schedule?

It seems as though the way it is set now, I am getting data when I start the app and it never updates thereafter. I don’t think the -AutoRefresh is the thing. Are we talking about scheduled endpoints?

Here’s the code I arrived at today (the call out to Get-CFANSLMStats is the thing that needs to be refreshed on a schedule):

$rslPages = @()

$licenseData = Get-CFANSLMStats

$rslPages += New-UDPage -Name "Main" -DefaultHomePage -Content {
    New-UDRow -Endpoint {
        foreach ($service in $licenseData.keys) {
            New-UDColumn -LargeSize 3 -MediumSize 6 -SmallSize 12 -Content {
                $Color = "PaleGreen"
                if ($licenseData.$service.DaemonStatus -notmatch "UP") {$Color = "IndianRed"}
                New-UDCard -Title "$($licenseData.$service.FullName) - $($licenseData.$service.DaemonName)" -Size Medium -Content {
                    New-UDRow {"Daemon status: " + $licenseData.$service.DaemonStatus}
                    New-UDRow {"Server: " + $licenseData.$service.LicenseServer}
                    if ($licenseData.$service.DaemonStatus -match "UP") {
                        New-UDRow {"Features available: " + ($licenseData.$service.features).length}
                        $totalSessions = 0
                        foreach ($f in $licenseData.$service.features) {
                            $totalSessions = $totalSessions + $licenseData.$service.$f.LicensesUsed
                        }
                        New-UDRow {"Total Active Feature Sessions: $($totalSessions)"}
                    }
                } -BackgroundColor $Color -Links @(
                    if ($licenseData.$service.DaemonStatus -match "UP") {
                        New-UDLink -Url "/$($licenseData.$service.Abbreviation)" -Text "More info" -FontColor "LightSlateGray" -Icon "question"
                    }
                )
            }
        }
    }
}

if ($licenseData.$service.DaemonStatus -match "UP") {
    foreach ($service in $licenseData.keys) {
        $headBGColor = "PaleGreen"
        if ($licenseData.$service.DaemonStatus -ne 'UP') { $headBGColor = "IndianRed"}
        $rslPages += New-UDPage -Name $licenseData.$service.Abbreviation -Content {
            New-UDRow -Endpoint {
                New-UDCard -Title $licenseData.$service.FullName -Content {
                    New-UDRow {"Daemon status: " + $licenseData.$service.DaemonStatus}
                    New-UDRow {"Server: " + $licenseData.$service.LicenseServer}
                    New-UDRow {"License File: " + $licenseData.$service.LicenseFile}
                    New-UDRow {"LMtools version: " + $licenseData.$service.DaemonVersion}
                    New-UDRow {"Features available: " + ($licenseData.$service.features).length}
                    $totalSessions = 0
                    foreach ($f in $licenseData.$service.features) {
                        $totalSessions = $totalSessions + $licenseData.$service.$f.LicensesUsed
                    }
                    New-UDRow {"Total Active Feature Sessions: $($totalSessions)"}
                } -BackgroundColor $headBGColor
            }
            New-UDRow -Endpoint {
                $featureNum = 0
                foreach ($feature in $licenseData.$service.Features) {
                    $featureNum++
                    if ( ($featureNum % 2) -eq 0 ) { $featureBackgroundColor = "LightGray"} else { $featureBackgroundColor = "White"}
                    New-UDcard -Endpoint {
                        New-UDCollection -Header "Feature: $($feature)" -Content {
                            New-UDCollectionItem -Content { "Total Licenses: $($licenseData.$service.$feature.LicensesTotal)" }
                            New-UDCollectionItem -Content { "Total Licenses: $($licenseData.$service.$feature.LicensesUsed)" }
                            if ($licenseData.$service.$feature.LicensesUsed -gt 0) {
                                New-UDColumn -LargeOffset 1 -LargeSize 12 -Content {
                                    New-UDCollectionitem -Content {
                                        New-UDCollection -Header "Active Sessions" -Content {
                                            foreach ($session in $licenseData.$service.$feature.LicenseUsers) {
                                                New-UDCollectionItem -Content {$session}
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    } -BackgroundColor $featureBackgroundColor
                }

            }
        }
    }
}

$Dashboard = New-UDDashboard -Title “Remote Sensing Lab Licensing Services” -Pages $rslPages
Start-UDDashboard -Port 80 -Dashboard $Dashboard

Looking good, i’m glad i could help.

There are several options, first is to gather the data within that endpoint. At this point refreshing the endpoint will update your data. But there could be a mayor drawback. If your function Get-CFANSLMStats takes a while to finish, that endpoint will also need that time to display the data (the page will be blank)

New-UDRow -Endpoint {
    $licenseData = Get-CFANSLMStats
        foreach ($service in $licenseData.keys) {
              (...)
        }
} -AutoRefresh -RefreshInterval 60 #seconds

Next option is the schedule the data refresh in an other runspace and only display the cached data on your pagge like this:

Get-UDDashboard | Stop-UDDashboard

$Every60Sec = New-UDEndpointSchedule -Every 60 -Second

$Schedule = New-UDEndpoint -Schedule $Every60Sec -Endpoint {
  $Cache:licenseData = Get-CFANSLMStats
}

$pages = @()

$pages += New-UDPage -Url "Main" -Content {
    New-UDRow -Endpoint {
        foreach ($service in $Cache:licenseData.keys) {
              (...)
        }
    } -AutoRefresh -RefreshInterval 60 #seconds
}

$ei = New-UDEndpointInitialization -Module "C:\<path to that module>\<modulename>.psm1"
$Dashboard = New-UDDashboard -Title 'dashbaord' -Page $pages -EndpointInitialization $ei 
Start-UDDashboard -Port 10001 -Dashboard $Dashboard -Endpoint @($Schedule) 

The scope $Cache: ist page wide accessable for every user on every page and every endpoint.
New-UDEndpointInitialization makes sure every new endpoint will know that function.

Thanks again, @psott! That seems to work well enough.

I have the function defined inside the same file that is creating the dashboard (looking for easy transportability). Thus, I have done it this way (because I wanted this to refresh every minute):


[Get-CFANSLMStats is defined above]

$rslPages = @()
$GCLMS = New-UDEndpointInitialization -Function Get-CFANSLMStats
$rslPages += New-UDPage -Name "Main" -DefaultHomePage -Content {
    New-UDRow -Endpoint {
[...]
    } -AutoRefresh -RefreshInterval 60 # Seconds
}

[...]

$Dashboard = New-UDDashboard -Title "Remote Sensing Lab Licensing Services" -Pages $rslPages -EndpointInitialization $GCLMS
Start-UDDashboard -Port 80 -Dashboard $Dashboard

That has provided the refresh I had wanted (if I stop a service, it turns red).

However, now the initial page load takes a little time (which I would expect) but none of the other pages load at all.

What might be going wrong that those other pages are not loading?

Thanks!

Looks as though something has gone weird. I am getting my “MORE INFO” links with URLs to generate but I don’t get the menu “burger” in the upper left corner with the list of pages.

Something appears to have gone wrong while i was making changes and pages are not longer getting added properly. Back to earlier commits, I guess.

If anyone has input on the refresh stuff or if you’ve messed up pages before and have thoughts, let me know.

P.S. I am used to looking at page source to help me diagnose problems but that doesn’t seem to work with the way this renders; how are others looking at with PoshUD spits out to see where their problems might be?

Well, version control for the win!

I was able to restore an older version with working pages and re-add the color styling for UP and DOWN daemons. I looked at the diffs for half a second but decided I didn’t need to know what happened. I merged that branch back to master and my Dashboard is working again.

Back to getting scheduling working…

Well I have added some scheduling to the working code (which loads pretty quick) but I am seeing a couple of issues now.

  1. The “Main” page takes multiple minutes to load
  2. The subpages do not load at all

Here’s the code I am using. Basically, if I remove the -Autorefresh and EndpointInitialization stuff it works well.

As ever, whatever time and attention anyone might give is very appreciated.

Thanks!

Get-UDDashboard | Stop-UDDashboard
function Get-CFANSLMStats {

    $KnownLMUtil = @(
        "C:\Program Files\QTModeler_8071_ux64\lmutil.exe", # LMUtil from QT Modeler
        "C:\Program Files (x86)\Exelis\IDL83\bin\bin.x86\lmutil.exe", # LMUtil from Exelis
        "C:\Program Files (x86)\Hexagon\Geospatial Licensing 2018\program\lmutil.exe" # LMUtil from Hexagon
    )

    $servicesObj = @(
        [PSCustomObject]@{
            ServiceAbbr = "fake"
            ServiceName = "Fake Service Test"
            Port        = "27017"
            Server      = "cfans-rsl01.oit.umn.edu"
        },
        [PSCustomObject]@{
            ServiceAbbr = "ecog9"
            ServiceName = "eCognition 9"
            Port        = "27000"
            Server      = "cfans-rsl01.oit.umn.edu"
        },
        [PSCustomObject]@{
            ServiceAbbr = "qtm"
            ServiceName = "QT Modeler"
            Port        = "27001"
            Server      = "cfans-rsl01.oit.umn.edu"
        }
        [PSCustomObject]@{
            ServiceAbbr = "interg"
            ServiceName = "Intergraph"
            Port        = "27002"
            Server      = "cfans-rsl01.oit.umn.edu"
        }
        [PSCustomObject]@{
            ServiceAbbr = "exelis"
            ServiceName = "Exelis"
            Port        = "27004"
            Server      = "cfans-rsl01.oit.umn.edu"
        }
    )

    foreach ($lm in $KnownLMUtil) {
        if (Test-Path $lm) {
            #TODO: Fix the search for working LMUtil?
            $LicenseManagerUtil = $lm
        }
    }

    ForEach ($ser in $servicesObj) {
        $port = $ser.Port
        $server = $ser.Server
        $abbr = $ser.ServiceAbbr
        $fullName = $ser.ServiceName
        $licenseInfo = (&$LicenseManagerUtil lmstat -a -c $port@$Server).Split("`r").Trim()
        if ($licenseInfo[4] -match "Error") {
            $licenseObj += @{
                $ser.ServiceAbbr =
                [PSCustomObject]@{
                    FullName      = $fullName
                    Abbreviation  = $abbr
                    DaemonName    = "DOWN"
                    DaemonStatus  = "DOWN"
                    DaemonVersion = "DOWN"
                    LicenseServer = "$($server)"
                    LicenseFile   = "DOWN"
                    Features      = @()
                }
            }
        }
        else {
            $licenseObj += @{
                $ser.ServiceAbbr =
                [PSCustomObject]@{
                    FullName      = $fullName
                    Abbreviation  = $abbr
                    DaemonName    = (($licenseInfo[11]).Split()[0]).TrimEnd(":")
                    DaemonStatus  = ($licenseInfo[11]).Split()[1]
                    DaemonVersion = ($licenseInfo[11]).Split()[2]
                    LicenseServer = ((($licenseInfo[4]) -Split ":")[1]).Trim()
                    LicenseFile   = ((($licenseInfo[5]) -Split ":\s")[1]).Trim().TrimEnd(":")
                    Features      = @()
                }
            }
        }
        foreach ($line in $licenseInfo) {
            if ($line -match "Users of") {
                $feature = (($line -Split " ")[2]).TrimEnd(":")
                $licenseObj.$abbr.Features += $Feature
                $licenseObj.$abbr | Add-Member -Name $Feature -MemberType NoteProperty -Value @{
                    LicensesTotal = ($line -Split " ")[6]
                    LicensesUsed  = ($line -Split " ")[12]
                    LicenseUsers  = @()
                }
            }
        }

        for ($i = 0; $i -le $licenseObj.$abbr.Features.Length - 1; $i++) {
            $feature = $licenseObj.$abbr.Features[$i]
            if ($licenseObj.$abbr.$feature.LicensesUsed -gt 0) {
                $userInfo = (&$LicenseManagerUtil lmstat -c $port@$Server -f $feature).Split("`r").Trim()
                foreach ($l in $userInfo) {
                    if ($l -match ", start") {
                        $licenseObj.$abbr.$feature.LicenseUsers += $l
                    }
                }
            }
        }

    }
    $licenseObj
}


$every60Seconds = New-UDEndpointSchedule -Every 60 -Second

$Schedule = New-UDEndpoint -Schedule $every60Seconds -Endpoint {
    $Cache:licenseData = Get-CFANSLMStats
}

$rslPages = @()

#$Cache:licenseData = Get-CFANSLMStats

$rslPages += New-UDPage -Name "Main" -DefaultHomePage -Content {
    New-UDRow -Endpoint {
        foreach ($service in $Cache:licenseData.keys) {
            New-UDColumn -LargeSize 3 -MediumSize 6 -SmallSize 12 -Content {
                $Color = "PaleGreen"
                if ($Cache:licenseData.$service.DaemonStatus -ne "UP") {$Color = "IndianRed"}
                New-UDCard -Title "$($Cache:licenseData.$service.FullName) - $($Cache:licenseData.$service.DaemonName)" -Size Medium -Content {
                    New-UDRow {"Daemon status: " + $Cache:licenseData.$service.DaemonStatus}
                    New-UDRow {"Server: " + $Cache:licenseData.$service.LicenseServer}
                    New-UDRow {"Features available: " + ($Cache:licenseData.$service.features).length}
                    $totalSessions = 0
                    foreach ($f in $Cache:licenseData.$service.features) {
                        $totalSessions = $totalSessions + $Cache:licenseData.$service.$f.LicensesUsed
                    }
                    New-UDRow {"Total Active Feature Sessions: $($totalSessions)"}
                } -BackgroundColor $Color -Links @(
                    if ($Cache:licenseData.$service.DaemonStatus -eq "UP") {
                        New-UDLink -Url "/$($Cache:licenseData.$service.Abbreviation)" -Text "More info" -FontColor "LightSlateGray" -Icon "question"
                    }
                )
            }
        }
    } -AutoRefresh -RefreshInterval 60 # seconds
}

foreach ($service in $Cache:licenseData.keys) {
    $headBGColor = "PaleGreen"
    if ($Cache:licenseData.$service.DaemonStatus -ne 'UP') { $headBGColor = "IndianRed"}
    $rslPages += New-UDPage -Name $Cache:licenseData.$service.Abbreviation -Content {
        New-UDRow -Endpoint {
            New-UDCard -Title $Cache:licenseData.$service.FullName -Content {
                New-UDRow {"Daemon status: " + $Cache:licenseData.$service.DaemonStatus}
                New-UDRow {"Server: " + $Cache:licenseData.$service.LicenseServer}
                New-UDRow {"License File: " + $Cache:licenseData.$service.LicenseFile}
                New-UDRow {"LMtools version: " + $Cache:licenseData.$service.DaemonVersion}
                New-UDRow {"Features available: " + ($Cache:licenseData.$service.features).length}
                $totalSessions = 0
                foreach ($f in $Cache:licenseData.$service.features) {
                    $totalSessions = $totalSessions + $Cache:licenseData.$service.$f.LicensesUsed
                }
                New-UDRow {"Total Active Feature Sessions: $($totalSessions)"}
            } -BackgroundColor $headBGColor
        } -AutoRefresh -RefreshInterval 60 # seconds
        New-UDRow -Endpoint {
            $featureNum = 0
            foreach ($feature in $Cache:licenseData.$service.Features) {
                $featureNum++
                if ( ($featureNum % 2) -eq 0 ) { $featureBackgroundColor = "LightGray"} else { $featureBackgroundColor = "White"}
                New-UDcard -Endpoint {
                    New-UDCollection -Header "Feature: $($feature)" -Content {
                        New-UDCollectionItem -Content { "Total Licenses: $($Cache:licenseData.$service.$feature.LicensesTotal)" }
                        New-UDCollectionItem -Content { "Total Licenses: $($Cache:licenseData.$service.$feature.LicensesUsed)" }
                        if ($Cache:licenseData.$service.$feature.LicensesUsed -gt 0) {
                            New-UDColumn -LargeOffset 1 -LargeSize 12 -Content {
                                New-UDCollectionitem -Content {
                                    New-UDCollection -Header "Active Sessions" -Content {
                                        foreach ($session in $Cache:licenseData.$service.$feature.LicenseUsers) {
                                            New-UDCollectionItem -Content {$session}
                                        }
                                    }
                                }
                            }
                        }
                    }
                } -BackgroundColor $featureBackgroundColor
            }

        } -AutoRefresh -RefreshInterval 60 # seconds
    }
}

$ei = New-UDEndpointInitialization -Function Get-CFANSLMStats
$Dashboard = New-UDDashboard -Title "Remote Sensing Lab Licensing Services" -Pages $rslPages -EndpointInitialization $ei
Start-UDDashboard -Port 80 -Dashboard $Dashboard -Endpoint @($Schedule)