3 years ago
SDT Dashboard Widget
I would like to see a dashboard widget that can display upcoming SDTs. We have multiple sites and being able to show upcoming maintenance windows for things like circuits would be useful for onsite...
Sure. Here's the code. PowerShell script. Should be self-explanatory.
#Requires -Version 5.1 <# Description - Update-WidgetDS_with_SDT.ps1 PS script to interrogate LogicMonitor REST API and modify target text widget with SDT data #> Set-Strictmode -Version Latest $ErrorActionPreference = 'Stop' $Global:ProgressPreference = 'SilentlyContinue' $Global:VerbosePreference = 'SilentlyContinue' $VerbosePreference = 'Continue' ####################### VARS ########################### # Initialize Variables [string]$accessId = '##LM.ACCESS.ID##' [string]$accessKey = '##LM.ACCESS.KEY##' [string]$company = '##LM.COMPANY##' [string]$hostname = '##SYSTEM.HOSTNAME##' [string]$dashboardId = '##DASHBOARD.ID##' [string]$dashboardWidgetID = '##WIDGET.ID##' $script_name = 'Update-WidgetDS_with_SDT.ps1' $version = '1.2a' [string]$date_formatted = (Get-Date -format "MMddyyyy_HHmm").ToString() [string]$limit = 5000 #5 when debugging [pscustomobject]$API_reply = @{} $ReportSDTs = [System.Collections.Generic.List[PSObject]]::new() $style = "<style>BODY{font-family:Tahoma; font-size:14px;}" $style = $style + "TABLE{border: 1px solid black; border-collapse: collapse; font-size:12px;}" $style = $style + "TH{border-bottom: 1px solid #ddd; background: #dddddd; padding: 5px; height: 50px;}" $style = $style + "TD{border-bottom: 1px solid #ddd; padding: 5px; }" $style = $style + "TR:nth-child(even) {background-color: #F5F5F5;}" $style = $style + "TR:hover {background-color: #00FFFF;}" $style = $style + "</style>" # C# class to create callback $code = @" public class SSLHandler { public static System.Net.Security.RemoteCertificateValidationCallback GetSSLHandler() { return new System.Net.Security.RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; }); } } "@ #compile the class if ("SSLHandler" -as [type]) {} else { Add-Type -TypeDefinition $code } #disable checks using new class [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [SSLHandler]::GetSSLHandler() ####################### FUNCTIONS ########################### Function Get-elapsedseconds { [CmdletBinding()] Param( [Parameter(Mandatory=$True)] [int64]$seconds ) [string]$elapsed = '' [string]$s = '' [string]$col = ':' $ts = [timespan]::fromseconds($seconds) if ($ts.Days) { $elapsed = "$($ts.Days)d" } if ($ts.Hours) { If ($ts.Days) { $elapsed += $col }; $elapsed += "$($ts.Hours)h" } if ($ts.Minutes) { If ($ts.Hours -or $ts.Days) { $elapsed += $col }; $elapsed += "$($ts.Minutes)m" } if ($ts.Seconds) { If ($ts.Days -or $ts.Hours -or $ts.Minutes) { } else { $elapsed = "$($ts.Seconds)s" }} $elapsed } Function Construct-request { [CmdletBinding()] Param( [Parameter(Mandatory=$True)] [string]$resource_passed ) Write-Verbose "Received input: '$resource_passed'" <# Use TLS 1.2 #> [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 <# request details #> $httpVerb = 'GET' $resourcePath = $resource_passed $queryParams = '?offset=0' + "&size=$limit" <# Construct URL #> $url = 'https://' + $company + '.logicmonitor.com/santaba/rest' + $resourcePath + $queryParams <# Get current time in milliseconds #> $epoch = [Math]::Round((New-TimeSpan -start (Get-Date -Date "1/1/1970") -end (Get-Date).ToUniversalTime()).TotalMilliseconds) <# Concatenate Request Details #> $requestVars = $httpVerb + $epoch + $resourcePath <# Construct Signature #> $hmac = New-Object System.Security.Cryptography.HMACSHA256 $hmac.Key = [Text.Encoding]::UTF8.GetBytes($accessKey) $signatureBytes = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($requestVars)) $signatureHex = [System.BitConverter]::ToString($signatureBytes) -replace '-' $signature = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($signatureHex.ToLower())) <# Construct Headers #> $auth = 'LMv1 ' + $accessId + ':' + $signature + ':' + $epoch $headers = @{ "Authorization" = "$auth" "Content-Type" = "application/json" "X-Version" = "2" } <# Make Request #> Try { $response = [System.Collections.Generic.List[Object]]::new() $response = Invoke-RestMethod -Uri $url -Method $httpVerb -Header $headers } Catch { Write-Warning "ERROR getting connected!`t$($_.Exception.Message)!"; return } $response } Function Update-widget { [CmdletBinding()] Param( [Parameter(Mandatory=$True)] [string]$resource_passed, [Parameter(Mandatory=$True)] $data_passed ) <# Use TLS 1.2 #> [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 <# request details #> $httpVerb = 'PUT' $resourcePath = $resource_passed $data = $data_passed $queryParams = '?offset=0' + "&size=$limit" <# Construct URL #> $url = 'https://' + $company + '.logicmonitor.com/santaba/rest' + $resourcePath <# Get current time in milliseconds #> $epoch = [Math]::Round((New-TimeSpan -start (Get-Date -Date "1/1/1970") -end (Get-Date).ToUniversalTime()).TotalMilliseconds) <# Concatenate Request Details #> $requestVars = $httpVerb + $epoch + $data + $resourcePath <# Construct Signature #> $hmac = New-Object System.Security.Cryptography.HMACSHA256 $hmac.Key = [Text.Encoding]::UTF8.GetBytes($accessKey) $signatureBytes = $hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($requestVars)) $signatureHex = [System.BitConverter]::ToString($signatureBytes) -replace '-' $signature = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($signatureHex.ToLower())) <# Construct Headers #> $auth = 'LMv1 ' + $accessId + ':' + $signature + ':' + $epoch $headers = @{ "Authorization" = "$auth" "Content-Type" = "application/json" "X-Version" = "2" } <# Make Request #> Try { $response = [System.Collections.Generic.List[Object]]::new() $response = Invoke-RestMethod -Uri $url -Method $httpVerb -Body $data -Header $headers } Catch { $ret = @{ "errormsg" = $_.Exception.Message "statuscode" = $_.Exception.Response.StatusCode.value__ "statuscodemsg" = $_.Exception.Response.StatusCode.ToString() "statusmsg" = $_.Exception.Response.StatusDescription } Write-Warning "ERROR updating widget! Error: $($ret.errormsg)!"; exit 21 } $response } ####################### GET REPORT ID AND WIDGET ID ###################### if ([string]::IsNullOrWhiteSpace($dashboardId) -or [string]::IsNullOrWhiteSpace($dashboardWidgetID) -or [string]::IsNullOrWhiteSpace($accessId) -or [string]::IsNullOrWhiteSpace($accessKey) -or [string]::IsNullOrWhiteSpace($company) -or [string]::IsNullOrWhiteSpace($hostname)) { Write-Warning "ERROR getting widget locations!"; exit 1 } Write-Verbose "Got this for widget location: '$dashboardId','$dashboardWidgetID'" ####################### GET DATA ###################### Write-Verbose "Running script '$script_name' version $version." Write-Verbose "Getting all SDTs, please wait..." $API_reply = Construct-request '/sdt/sdts' If ($API_reply) { Write-Verbose "OK, received $($API_reply.total) rows of data" } else { Write-Warning "No reply received from API call!"; exit 2 } ####################### MUNCH DATA ###################### # Build the output data: $ReportSDTs.Clear() @($API_reply.items).ForEach({ #[-2] when debugging $curr_row = $_ $obj1 = [pscustomobject]@{ 'id' = $_.id 'Device' = $(If ($curr_row.PSObject.Properties['deviceDisplayName']) { $curr_row.PSObject.Properties['deviceDisplayName'].value.ToString() } else {''}) 'DataSourceInstance'= $(If ($curr_row.PSObject.Properties['dataSourceInstanceName']) { $curr_row.PSObject.Properties['dataSourceInstanceName'].value.ToString() } else {''}) #'GroupName' = $(If ($curr_row.PSObject.Properties['deviceGroupFullPath']) { $curr_row.PSObject.Properties['deviceGroupFullPath'].value.ToString() } else {''}) # no longer in API v2!!! 'Effective' = $_.isEffective 'Type' = $_.type 'Duration' = Get-elapsedseconds ($_.duration*60) #duration of SDT in minutes! 'StartTime' = $_.startDateTimeOnLocal.ToString() -replace '(?xms) :\d\d \s+','' -replace '(?xms) P(?:S|D)T','' 'EndTime' = $_.endDateTimeOnLocal.ToString() -replace '(?xms) :\d\d \s+','' -replace '(?xms) P(?:S|D)T','' 'SDTtype' = $_.sdtType # 1 -one time, 4 - recurring #'timezone' = $_.timezone # it should be 'America/Los_Angeles' always, so don't even bother to show it 'Admin' = $_.admin # name of luser who created SDT 'Comment' = $_.comment } $ReportSDTs.Add($obj1) >$null }) Write-Verbose "OK, got info for $(@($ReportSDTs).Count) SDTs." ####################### OUTPUT DATA ###################### [string]$content = $ReportSDTs | Sort-Object -Property Device | Select-Object -Property Device,DataSourceInstance,Effective,SDTtype,Type,Duration,StartTime,EndTime,Admin,Comment | ConvertTo-Html -Head $style $obj1 = @{} $obj1 = [ordered]@{ 'type' = "text" 'dashboardId' = $dashboardId 'name' = "SDT report as of: $((Get-Date -format "dddd MM/dd h:mm tt").ToString()). Records found: $(@($ReportSDTs).Count). Enjoy." 'content' = $content } $dataJSONtext = $obj1 |ConvertTo-Json -Depth 6 $reply2 = [System.Collections.Generic.List[PSObject]]::new() $reply2 = Update-widget "/dashboard/widgets/$dashboardWidgetID" $dataJSONtext If (!$reply2) { Write-Warning "No data received updating widget, results unknown!"; exit 3 } Write-Host "html_length=$($content.Length)" Write-Host "rows_returned=$(@($ReportSDTs).Count)" exit 0