Skip to content

Instantly share code, notes, and snippets.

@TLaborde
Created April 12, 2016 07:38
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TLaborde/22359e9029f3bc03872c208406d64d5d to your computer and use it in GitHub Desktop.
Save TLaborde/22359e9029f3bc03872c208406d64d5d to your computer and use it in GitHub Desktop.
Using powershell for "realtime" notification on a dashboard
As requested on reddit, here is some information about how i made a "realtime" dashboard.
Flow
===
1. a task grab data with a scheduled job. It saves the data in json in two places: one static folder, which overwrite existing data, and in a temporary folder, to be a new "event".
2. a websocket deamon (websocketd) run a powershell script listening to changes in the temporary folder. When a change happens, the new data is read and sent thru the websocket
3. the frontend update the data with what came thru the websocket. If the browser does not support websocket, it will instead pull the data from time to time
Grabbing Data and saving as JSON
================================
This part is easy. For example, we will get the latest article about Pokemon. Here is the script:
$news = invoke-restmethod -uri "https://news.google.com/news?q=pokemon&output=rss"
$out = $news |sort -property @{Expression={get-date $_.pubdate}; Ascending=$false} |select title,link -First 1
$out | ConvertTo-Json | Out-File "C:\InetPub\mydashboard\static\data\news.json" -Encoding utf8
[pscustomobject]@{source="news";data=$out}| ConvertTo-Json -Compress | Out-File "C:\temp\$((get-date).ToFileTime()).json" -Encoding utf8
Yes, code is crappy, sorry for that.
Sending the data with websocket
===============================
Since I didn't bother with a proper queue system, just storing files on disk and reading them, when you have more than one concurrent access to your socket, the thread will fights for opening and reading the file. So we use a global mutex! Enjoy the code without comments...
$MutexName = "Global\ReadFile"
$MutexWasCreated = $false;
try {
$Mutex = [System.Threading.Mutex]::OpenExisting($MutexName);
}catch{
$Mutex = New-Object System.Threading.Mutex($false,$MutexName,[ref]$MutexWasCreated);
}
$watcher = New-Object System.IO.FileSystemWatcher -Property @{Path = 'C:\Temp';
Filter = '*.json';
NotifyFilter = [System.IO.NotifyFilters]'FileName,LastWrite'}
Register-ObjectEvent -InputObject $watcher -EventName Changed -SourceIdentifier FileCreated #-Action $CreatedActions
while ($true){
$events = @(Get-Event -SourceIdentifier FileCreated -ErrorAction SilentlyContinue )
foreach($event in $events) {
$Mutex.WaitOne() | Out-Null
try {
Get-Content $event.sourceEventArgs.FullPath -ReadCount 0 | Write-Host
} catch {
Write-Host $_
}
$Mutex.ReleaseMutex() | Out-Null
Remove-Event -EventIdentifier $event.EventIdentifier
}
$null = Wait-Event -SourceIdentifier FileCreated
}
Explanation of the script: first we try to find the global mutex, or we create it if we are the first thread created. Then we put a watcher on the temp folder, and we wait for new files to be created. When a file is created, we grab the mutex ASAP, read the file, send the data to output, and release the mutex.
This script is launched by https://github.com/joewalnes/websocketd With a command line (launch as a task at the start of the server) that look like that:
``websocketd.exe -port 8081 powershell -nologo -sta -noprofile -file C:\Myscriptpath\WebSocket.ps1``
STA for single thread is not mandatory after PS2.0 I think. Nologo because we don't want to send trash down the pipe, noprofile for faster launch of new thread, and port 8081 because I use IIS to do reverse proxy with SSL offloading (since websocketd is not very good at doing TLS handshake on windows)
Front-End part
==============
HTML part
---------
My static index.html contains a named span, with a default text that appears while everything is loading:
<span id="LatestNews" class="semi-bold">Loading latest news...</span>
On the Javascript, I use Jquery and the reconnecting-websocket lib (https://github.com/joewalnes/reconnecting-websocket) to pull the data. Code excerpt:
$(document).ready(function() {
supportsWebSockets = 'WebSocket' in window || 'MozWebSocket' in window;
if (supportsWebSockets) {
$('#realTime').show();
var dataflow = new ReconnectingWebSocket("wss://rashboard:8080");
dataflow.onmessage = function(evt) {
updateData(evt)
};
function updateData(evt) {
msg = JSON.parse(evt.data);
switch (msg.source) {
-- snip --
case "news":
updateNews(msg.data);
break;
-- snip --
}
}
} else {
//no websocket, doing it old school
-- snip --
setInterval(pollNews, 1000 * 60 * 5);
-- snip --
}
-- snip --
pollNews();
-- snip --
function pollNews() {
$.ajax({
url: "/data/news.json",
success: function(data) {
updateNews(data);
},
});
};
function updateNews(data) {
var entry = data;
var item_html = '<a target="_blank" href="' + entry.link + '">' + entry.title + '</a>';
if ($('#LatestNews').html() != item_html) {
$('#LatestNews').fadeOut(1000, function() {
$(this).html(item_html).fadeIn(1000);
});
}
}
});
So, i have two helper functions: one grab the data, and the other set the data in the page. For the logical part, I check if i can use websocket. If i can, i connect, and define what happen when something come down the wire. When a message arrives we use the data to update the frontend.
If we don't have a websocket, we define a timeout function that grab the data every hour and update it. We also call the grab function once to set the proper data when the user load the page the first time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment