Skip to content

Commit

Permalink
Show dev nodes on dashboard
Browse files Browse the repository at this point in the history
Fixes #145

Signed-off-by: Dave Thaler <[email protected]>
  • Loading branch information
dthaler committed Oct 9, 2024
1 parent 7415339 commit 2445a89
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 116 deletions.
257 changes: 147 additions & 110 deletions OrcanodeMonitor/Core/Fetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public class Fetcher
{
private static TimeZoneInfo _pacificTimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles");
private static HttpClient _httpClient = new HttpClient();
private static string _orcasoundFeedsUrl = "https://live.orcasound.net/api/json/feeds";
private static string _orcasoundProdFeedsUrl = "https://live.orcasound.net/api/json/feeds";
private static string _orcasoundDevFeedsUrl = "https://dev.orcasound.net/api/json/feeds";
private static string _dataplicityDevicesUrl = "https://apps.dataplicity.com/devices/";
private static string _orcaHelloHydrophonesUrl = "https://aifororcasdetections2.azurewebsites.net/api/hydrophones";
private static DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
Expand Down Expand Up @@ -447,147 +448,183 @@ public async static Task UpdateDataplicityDataAsync(OrcanodeMonitorContext conte
}
}

/// <summary>
/// Update the current list of Orcanodes using data from orcasound.net.
/// </summary>
/// <param name="context">Database context to update</param>
/// <returns></returns>
public async static Task UpdateOrcasoundDataAsync(OrcanodeMonitorContext context)
private async static Task<JsonElement?> GetOrcasoundDataAsync(OrcanodeMonitorContext context, string url)
{
try
{
string json = await _httpClient.GetStringAsync(_orcasoundFeedsUrl);
string json = await _httpClient.GetStringAsync(url);
if (json.IsNullOrEmpty())
{
return;
return null;
}
dynamic response = JsonSerializer.Deserialize<ExpandoObject>(json);
if (response == null)
{
return;
return null;
}
JsonElement dataArray = response.data;
if (dataArray.ValueKind != JsonValueKind.Array)
{
return;
return null;
}
return dataArray;
}
catch (Exception ex)
{
string msg = ex.ToString();
return null;
}
}

var foundList = context.Orcanodes.ToList();
private static void UpdateOrcasoundNode(JsonElement feed, List<Orcanode> foundList, List<Orcanode> unfoundList, OrcanodeMonitorContext context)
{
if (!feed.TryGetProperty("id", out var feedId))
{
return;
}
if (!feed.TryGetProperty("attributes", out JsonElement attributes))
{
return;
}
if (!attributes.TryGetProperty("name", out var name))
{
return;
}
string orcasoundName = name.ToString();
if (!attributes.TryGetProperty("dataplicity_id", out var dataplicity_id))
{
return;
}
bool hidden = false;
if (attributes.TryGetProperty("hidden", out var hiddenElement))
{
hidden = hiddenElement.GetBoolean();
}
string dataplicitySerial = dataplicity_id.ToString();

// Create a list to track what nodes are no longer returned.
var unfoundList = foundList.ToList();
// Remove the found node from the unfound list.
Orcanode? oldListNode = unfoundList.Find(a => a.OrcasoundFeedId == feedId.ToString());
if (oldListNode != null)
{
unfoundList.Remove(oldListNode);
}

foreach (JsonElement feed in dataArray.EnumerateArray())
Orcanode? node = null;
node = FindOrcanodeByOrcasoundFeedId(foundList, feedId.ToString());
if (node == null)
{
// We didn't used to store the feed ID, only the name, so try again by name.
Orcanode? possibleNode = FindOrcanodeByOrcasoundName(foundList, orcasoundName);
if ((possibleNode != null) && possibleNode.OrcasoundFeedId.IsNullOrEmpty())
{
if (!feed.TryGetProperty("id", out var feedId))
{
continue;
}
if (!feed.TryGetProperty("attributes", out JsonElement attributes))
{
continue;
}
if (!attributes.TryGetProperty("name", out var name))
{
continue;
}
string orcasoundName = name.ToString();
if (!attributes.TryGetProperty("dataplicity_id", out var dataplicity_id))
{
continue;
}
bool hidden = false;
if (attributes.TryGetProperty("hidden", out var hiddenElement))
{
hidden = hiddenElement.GetBoolean();
}
string dataplicitySerial = dataplicity_id.ToString();

// Remove the found node from the unfound list.
Orcanode? oldListNode = unfoundList.Find(a => a.OrcasoundFeedId == feedId.ToString());
node = possibleNode;
oldListNode = unfoundList.Find(a => a.OrcasoundName == orcasoundName);
if (oldListNode != null)
{
unfoundList.Remove(oldListNode);
}
}
}

Orcanode? node = null;
node = FindOrcanodeByOrcasoundFeedId(foundList, feedId.ToString());
// See if we can find a node by dataplicity ID, so that if a node
// shows up in dataplicity first and Orcasite later, we don't create a
// duplicate entry.
Orcanode? dataplicityNode = null;
if (!dataplicitySerial.IsNullOrEmpty())
{
dataplicityNode = FindOrcanodeByDataplicitySerial(foundList, dataplicitySerial, out OrcanodeOnlineStatus oldStatus);
if (dataplicityNode != null)
{
if (node == null)
{
// We didn't used to store the feed ID, only the name, so try again by name.
node = FindOrcanodeByOrcasoundName(foundList, orcasoundName);
node = dataplicityNode;
}

// See if we can find a node by dataplicity ID, so that if a node
// shows up in dataplicity first and Orcasite later, we don't create a
// duplicate entry.
Orcanode? dataplicityNode = null;
if (!dataplicitySerial.IsNullOrEmpty())
else if (node != dataplicityNode)
{
dataplicityNode = FindOrcanodeByDataplicitySerial(foundList, dataplicitySerial, out OrcanodeOnlineStatus oldStatus);
if (dataplicityNode != null)
{
if (node == null)
{
node = dataplicityNode;
}
else if (node != dataplicityNode)
{
// We have duplicate nodes to merge. In theory we shouldn't have any
// node state for the dataplicity-only node. (TODO: verify this)
node.DataplicityDescription = dataplicityNode.DataplicityDescription;
node.DataplicityName = dataplicityNode.DataplicityName;
node.DataplicityOnline = dataplicityNode.DataplicityOnline;
node.AgentVersion = dataplicityNode.AgentVersion;
node.DiskCapacity = dataplicityNode.DiskCapacity;
node.DiskUsed = dataplicityNode.DiskUsed;
node.DataplicityUpgradeAvailable = dataplicityNode.DataplicityUpgradeAvailable;
context.Orcanodes.Remove(dataplicityNode);
}
}
// We have duplicate nodes to merge. In theory we shouldn't have any
// node state for the dataplicity-only node. (TODO: verify this)
node.DataplicityDescription = dataplicityNode.DataplicityDescription;
node.DataplicityName = dataplicityNode.DataplicityName;
node.DataplicityOnline = dataplicityNode.DataplicityOnline;
node.AgentVersion = dataplicityNode.AgentVersion;
node.DiskCapacity = dataplicityNode.DiskCapacity;
node.DiskUsed = dataplicityNode.DiskUsed;
node.DataplicityUpgradeAvailable = dataplicityNode.DataplicityUpgradeAvailable;
context.Orcanodes.Remove(dataplicityNode);
}
}
}

if (node == null)
{
node = CreateOrcanode(context.Orcanodes);
node.OrcasoundName = name.ToString();
}
if (node == null)
{
node = CreateOrcanode(context.Orcanodes);
node.OrcasoundName = name.ToString();
}

if (!dataplicitySerial.IsNullOrEmpty())
{
if (!node.DataplicitySerial.IsNullOrEmpty() && dataplicitySerial != node.DataplicitySerial)
{
// TODO: The orcasound entry for the node changed its dataplicity_id.
}
node.DataplicitySerial = dataplicitySerial;
}
if (!dataplicitySerial.IsNullOrEmpty())
{
if (!node.DataplicitySerial.IsNullOrEmpty() && dataplicitySerial != node.DataplicitySerial)
{
// TODO: The orcasound entry for the node changed its dataplicity_id.
}
node.DataplicitySerial = dataplicitySerial;
}

if (node.OrcasoundFeedId.IsNullOrEmpty())
{
node.OrcasoundFeedId = feedId.ToString();
}
if (orcasoundName != node.OrcasoundName)
{
// We just detected a name change.
node.OrcasoundName = orcasoundName;
}
if (attributes.TryGetProperty("node_name", out var nodeName))
{
node.S3NodeName = nodeName.ToString();
}
if (attributes.TryGetProperty("bucket", out var bucket))
{
node.S3Bucket = bucket.ToString();
}
if (attributes.TryGetProperty("slug", out var slug))
{
node.OrcasoundSlug = slug.ToString();
}
if (attributes.TryGetProperty("visible", out var visible))
{
node.OrcasoundVisible = visible.GetBoolean();
}
if (node.OrcasoundFeedId.IsNullOrEmpty())
{
node.OrcasoundFeedId = feedId.ToString();
}
if (orcasoundName != node.OrcasoundName)
{
// We just detected a name change.
node.OrcasoundName = orcasoundName;
}
if (attributes.TryGetProperty("node_name", out var nodeName))
{
node.S3NodeName = nodeName.ToString();
}
if (attributes.TryGetProperty("bucket", out var bucket))
{
node.S3Bucket = bucket.ToString();
}
if (attributes.TryGetProperty("slug", out var slug))
{
node.OrcasoundSlug = slug.ToString();
}
if (attributes.TryGetProperty("visible", out var visible))
{
node.OrcasoundVisible = visible.GetBoolean();
}
}

private async static Task UpdateOrcasoundSiteDataAsync(OrcanodeMonitorContext context, string url, List<Orcanode> foundList, List<Orcanode> unfoundList)
{
JsonElement? dataArray = await GetOrcasoundDataAsync(context, url);
if (dataArray.HasValue)
{
foreach (JsonElement feed in dataArray.Value.EnumerateArray())
{
UpdateOrcasoundNode(feed, foundList, unfoundList, context);
}
}
}

/// <summary>
/// Update the current list of Orcanodes using data from orcasound.net.
/// </summary>
/// <param name="context">Database context to update</param>
/// <returns></returns>
public async static Task UpdateOrcasoundDataAsync(OrcanodeMonitorContext context)
{
var foundList = context.Orcanodes.ToList();

// Create a list to track what nodes are no longer returned.
var unfoundList = foundList.ToList();

try
{
await UpdateOrcasoundSiteDataAsync(context, _orcasoundProdFeedsUrl, foundList, unfoundList);
await UpdateOrcasoundSiteDataAsync(context, _orcasoundDevFeedsUrl, foundList, unfoundList);

// Mark any remaining unfound nodes as absent.
foreach (var unfoundNode in unfoundList)
Expand Down
20 changes: 20 additions & 0 deletions OrcanodeMonitor/Models/Orcanode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,26 @@ public OrcanodeOnlineStatus S3StreamStatus
}
}

private bool IsDev
{
get
{
if (this.S3Bucket.StartsWith("dev"))
{
return true;
}
if (this.DataplicityName.ToLower().StartsWith("dev"))
{
return true;
}
return false;
}
}

public string OrcasoundHost => IsDev ? "dev.orcasound.net" : "live.orcasound.net";

public string Type => IsDev ? "Dev" : "Prod";

public string OrcasoundOnlineStatusString {
get
{
Expand Down
16 changes: 10 additions & 6 deletions OrcanodeMonitor/Pages/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<table>
<tr>
<th>Location</th>
<th>Type</th>
<!--
<th>Latest recorded</th>
<th>Latest uploaded</th>
Expand All @@ -21,9 +22,9 @@
<th><a href="https://www.dataplicity.com/app/" target="_blank">Dataplicity</a></th>
<th>Agent Version</th>
<th>SD Card Util.</th>
<th>Orcasound</th>
<th><a href="https://live.orcasound.net/listen" target="_blank">Orcasound</a></th>
<th>S3 Stream</th>
<th>OrcaHello</th>
<th><a href="https://aifororcas2.azurewebsites.net/hydrophones" target="_blank">OrcaHello</a></th>
<!--
<th>Last OrcaHello Detection</th>
<th>Confidence</th>
Expand All @@ -35,6 +36,9 @@
<td title="@Html.DisplayFor(modelItem => item.DataplicityDescription)">
@Html.DisplayFor(modelItem => item.DisplayName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Type)
</td>
<!--
<td>
@Html.DisplayFor(modelItem => item.LatestRecordedLocal)
Expand Down Expand Up @@ -70,7 +74,7 @@
else
{
<td style="background-color: @Model.NodeOrcasoundBackgroundColor(item)">
<a href="https://live.orcasound.net/listen/@Html.DisplayFor(modelItem => item.OrcasoundSlug)" style="color: @Model.NodeOrcasoundTextColor(item)" target="_blank">
<a href="https://@Html.DisplayFor(modelItem => item.OrcasoundHost)/listen/@Html.DisplayFor(modelItem => item.OrcasoundSlug)" style="color: @Model.NodeOrcasoundTextColor(item)" target="_blank">
@Html.DisplayFor(modelItem => item.OrcasoundStatus)
</a>
</td>
Expand Down Expand Up @@ -119,13 +123,13 @@
<b>Dataplicity Online</b>: Dataplicity connection is up.
</li>
<li>
<b>Orcasound Absent</b>: live.orcasound.net does not know about the node.
<b>Orcasound Absent</b>: orcasound.net does not know about the node.
</li>
<li>
<b>Orcasound Hidden</b>: live.orcasound.net knows about but does not display the node.
<b>Orcasound Hidden</b>: orcasound.net knows about but does not display the node.
</li>
<li>
<b>Orcasound Online</b>: live.orcasound.net knows about the node.
<b>Orcasound Online</b>: orcasound.net knows about the node.
</li>
<li>
<b>S3 Stream Absent</b>: No latest.txt file exists on S3 for this node.
Expand Down

0 comments on commit 2445a89

Please sign in to comment.