Skip to content

Commit

Permalink
Show dev nodes on dashboard (#147)
Browse files Browse the repository at this point in the history
* Show dev nodes on dashboard

Fixes #145

Signed-off-by: Dave Thaler <[email protected]>

* Store orcasound site (dev vs prod) in cosmos db

Signed-off-by: Dave Thaler <[email protected]>

---------

Signed-off-by: Dave Thaler <[email protected]>
  • Loading branch information
dthaler authored Oct 9, 2024
1 parent 7415339 commit 89d979e
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 116 deletions.
263 changes: 153 additions & 110 deletions OrcanodeMonitor/Core/Fetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ 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 _orcasoundProdSite = "live.orcasound.net";
private static string _orcasoundDevSite = "dev.orcasound.net";
private static string _orcasoundFeedsUrlPath = "/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 +449,188 @@ 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 site)
{
try
{
string json = await _httpClient.GetStringAsync(_orcasoundFeedsUrl);
string url = "https://" + site + _orcasoundFeedsUrlPath;
string json = await _httpClient.GetStringAsync(url);
if (json.IsNullOrEmpty())
{
return;
return null;
}
dynamic response = JsonSerializer.Deserialize<ExpandoObject>(json);

Check warning on line 462 in OrcanodeMonitor/Core/Fetcher.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.
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, string site)
{
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 (!site.IsNullOrEmpty())
{
node.OrcasoundHost = site;
}
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 site, List<Orcanode> foundList, List<Orcanode> unfoundList)
{
JsonElement? dataArray = await GetOrcasoundDataAsync(context, site);
if (dataArray.HasValue)
{
foreach (JsonElement feed in dataArray.Value.EnumerateArray())
{
UpdateOrcasoundNode(feed, foundList, unfoundList, context, site);
}
}
}

/// <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, _orcasoundProdSite, foundList, unfoundList);
await UpdateOrcasoundSiteDataAsync(context, _orcasoundDevSite, foundList, unfoundList);

// Mark any remaining unfound nodes as absent.
foreach (var unfoundNode in unfoundList)
Expand Down
24 changes: 24 additions & 0 deletions OrcanodeMonitor/Models/Orcanode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public Orcanode()
OrcasoundName = string.Empty;
OrcasoundSlug = string.Empty;
OrcasoundFeedId = string.Empty;
OrcasoundHost = string.Empty;
S3Bucket = string.Empty;
S3NodeName = string.Empty;
AgentVersion = string.Empty;
Expand Down Expand Up @@ -187,6 +188,11 @@ public Orcanode()
/// Audio stream status of most recent sample (defaults to absent).
/// </summary>
public OrcanodeOnlineStatus? AudioStreamStatus { get; set; }

/// <summary>
/// Orcasound site host (defaults to empty).
/// </summary>
public string OrcasoundHost { get; set; }

#endregion persisted

Expand Down Expand Up @@ -300,6 +306,24 @@ 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 Type => IsDev ? "Dev" : "Prod";

public string OrcasoundOnlineStatusString {
get
{
Expand Down
Loading

0 comments on commit 89d979e

Please sign in to comment.