-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Initial service scaffolding * Enumerate nodes in background * Get latest timestamps * Get manifest updated timestamps * Add a derived Status per node * Add initial dashboard web page * Add copyright notice and SPDX to cs files * Add initial API controller * Track recent events * Fix offline timestamp * Add IFTTT diagram to Design page * Add CI/CD workflow Signed-off-by: Dave Thaler <[email protected]>
- Loading branch information
Showing
91 changed files
with
75,542 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# For documentation on the format of this file, see | ||
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file | ||
|
||
version: 2 | ||
updates: | ||
|
||
- package-ecosystem: "github-actions" | ||
# Workflow files stored in the | ||
# default location of `.github/workflows` | ||
directory: "/" | ||
schedule: | ||
interval: "weekly" | ||
day: "saturday" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
name: CPP CI | ||
|
||
on: | ||
pull_request: | ||
|
||
concurrency: | ||
# Cancel any CI/CD workflow currently in progress for the same PR. | ||
# Allow running concurrently with any other commits. | ||
group: build-${{ github.event.pull_request.number || github.sha }} | ||
cancel-in-progress: true | ||
|
||
jobs: | ||
build_windows: | ||
strategy: | ||
matrix: | ||
configurations: [Debug, Release] | ||
runs-on: windows-latest | ||
env: | ||
# Configuration type to build. For documentation on how build matrices work, see | ||
# https://docs.github.com/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix | ||
BUILD_CONFIGURATION: ${{matrix.configurations}} | ||
|
||
steps: | ||
- uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 | ||
with: | ||
egress-policy: audit | ||
|
||
- id: skip_check | ||
uses: fkirc/skip-duplicate-actions@v5 | ||
with: | ||
cancel_others: 'false' | ||
paths_ignore: '["**.md"]' | ||
|
||
- uses: actions/checkout@v4 | ||
if: steps.skip_check.outputs.should_skip != 'true' | ||
with: | ||
submodules: 'recursive' | ||
|
||
- name: Build | ||
if: steps.skip_check.outputs.should_skip != 'true' | ||
run: | | ||
dotnet build OrcanodeMonitor.sln /p:Configuration=${{env.BUILD_CONFIGURATION}} /p:Platform="Any CPU" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/**/.vs/ | ||
/**/obj/ | ||
/**/bin/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 17 | ||
VisualStudioVersion = 17.10.34928.147 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrcanodeMonitor", "OrcanodeMonitor\OrcanodeMonitor.csproj", "{D45F546F-D66C-4FDC-B680-D457275221BD}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{D45F546F-D66C-4FDC-B680-D457275221BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{D45F546F-D66C-4FDC-B680-D457275221BD}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{D45F546F-D66C-4FDC-B680-D457275221BD}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{D45F546F-D66C-4FDC-B680-D457275221BD}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {C3612E55-DFF0-4939-9E5A-D7CFE36F536D} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Copyright (c) Orcanode Monitor contributors | ||
// SPDX-License-Identifier: MIT | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Mvc; | ||
|
||
namespace OrcanodeMonitor.Api | ||
{ | ||
[Route("api/[controller]")] | ||
[ApiController] | ||
public class MonitorController : ControllerBase | ||
{ | ||
public IActionResult Get() | ||
{ | ||
// Logic for handling GET requests | ||
return Ok("Hello from API!"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
using Microsoft.AspNetCore.Mvc; | ||
using System.Dynamic; | ||
using System.Text.Json; | ||
|
||
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 | ||
|
||
namespace OrcanodeMonitor.Api | ||
{ | ||
[Route("api/ifttt/v1/triggers/[controller]")] | ||
[ApiController] | ||
public class TestController : ControllerBase | ||
{ | ||
#if false | ||
// GET: api/ifttt/v1/triggers/test | ||
[HttpGet] | ||
public IEnumerable<string> Get() | ||
{ | ||
return new string[] { "value1", "value2" }; | ||
} | ||
#endif | ||
private JsonResult GetEvents(int limit) | ||
{ | ||
List<Core.OrcanodeEvent> latestEvents = Core.State.GetEvents(limit); | ||
var dataResult = new { data = latestEvents }; | ||
|
||
var jsonString = JsonSerializer.Serialize(dataResult); | ||
var jsonDocument = JsonDocument.Parse(jsonString); | ||
|
||
// Get the JSON data as an array. | ||
var jsonElement = jsonDocument.RootElement; | ||
|
||
return new JsonResult(jsonElement); | ||
} | ||
|
||
// GET: api/ifttt/v1/triggers/<TestController> | ||
[HttpGet] | ||
public JsonResult Get() | ||
{ | ||
return GetEvents(50); | ||
} | ||
|
||
// POST api/ifttt/v1/triggers/<TestController> | ||
[HttpPost] | ||
public IActionResult Post([FromBody] string value) | ||
{ | ||
try | ||
{ | ||
dynamic requestBody = JsonSerializer.Deserialize<ExpandoObject>(value); | ||
if (!requestBody.TryGetProperty("limit", out JsonElement limitElement)) | ||
{ | ||
return BadRequest("Invalid JSON data."); | ||
} | ||
int limit = 50; | ||
if (limitElement.TryGetInt32(out int explicitLimit)) | ||
{ | ||
limit = explicitLimit; | ||
} | ||
if (requestBody.TryGetProperty("triggerFields", out JsonElement triggerFields)) | ||
{ | ||
// TODO: use triggerFields to see if the caller only wants | ||
// events for a given node. | ||
} | ||
|
||
return GetEvents(limit); | ||
} | ||
catch (JsonException) | ||
{ | ||
return BadRequest("Invalid JSON data."); | ||
} | ||
} | ||
|
||
#if false | ||
// GET api/ifttt/v1/triggers/<TestController>/5 | ||
[HttpGet("{id}")] | ||
public string Get(int id) | ||
{ | ||
return "value"; | ||
} | ||
|
||
// POST api/ifttt/v1/triggers/<TestController> | ||
[HttpPost] | ||
public void Post([FromBody] string value) | ||
{ | ||
} | ||
|
||
// PUT api/ifttt/v1/triggers/<TestController>/5 | ||
[HttpPut("{id}")] | ||
public void Put(int id, [FromBody] string value) | ||
{ | ||
} | ||
|
||
// DELETE api/ifttt/v1/triggers/<TestController>/5 | ||
[HttpDelete("{id}")] | ||
public void Delete(int id) | ||
{ | ||
} | ||
#endif | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
// Copyright (c) Orcanode Monitor contributors | ||
// SPDX-License-Identifier: MIT | ||
using System; | ||
using System.Dynamic; | ||
using System.Text.Json; | ||
using System.Net.Http; | ||
using System.Text.Json.Nodes; | ||
using System.Xml.Linq; | ||
|
||
namespace OrcanodeMonitor.Core | ||
{ | ||
public class EnumerateNodesResult | ||
{ | ||
public List<Orcanode> NodeList { get; private set; } | ||
public bool Succeeded { get; set; } | ||
public DateTime Timestamp { get; private set; } | ||
|
||
public EnumerateNodesResult(DateTime timestamp) | ||
{ | ||
NodeList = new List<Orcanode>(); | ||
Succeeded = false; | ||
Timestamp = timestamp; | ||
} | ||
} | ||
public class Fetcher | ||
{ | ||
private static HttpClient _httpClient = new HttpClient(); | ||
private static string _orcasoundFeedsUrl = "https://live.orcasound.net/api/json/feeds"; | ||
private static DateTime _unixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); | ||
|
||
/// <summary> | ||
/// Get the current list of Orcanodes from orcasound.net. | ||
/// </summary> | ||
/// <returns>EnumerateNodesResult object</returns> | ||
public async static Task<EnumerateNodesResult> EnumerateNodesAsync() | ||
{ | ||
var result = new EnumerateNodesResult(DateTime.Now); | ||
string json = ""; | ||
try | ||
{ | ||
json = await _httpClient.GetStringAsync(_orcasoundFeedsUrl); | ||
if (json == "") | ||
{ | ||
return result; | ||
} | ||
dynamic response = JsonSerializer.Deserialize<ExpandoObject>(json); | ||
if (response == null) | ||
{ | ||
return result; | ||
} | ||
JsonElement dataArray = response.data; | ||
if (dataArray.ValueKind != JsonValueKind.Array) | ||
{ | ||
return result; | ||
} | ||
foreach (JsonElement feed in dataArray.EnumerateArray()) | ||
{ | ||
if (!feed.TryGetProperty("attributes", out JsonElement attributes)) | ||
{ | ||
continue; | ||
} | ||
if (!attributes.TryGetProperty("name", out var name)) | ||
{ | ||
continue; | ||
} | ||
if (!attributes.TryGetProperty("node_name", out var nodeName)) | ||
{ | ||
continue; | ||
} | ||
if (!attributes.TryGetProperty("bucket", out var bucket)) | ||
{ | ||
continue; | ||
} | ||
if (!attributes.TryGetProperty("slug", out var slug)) | ||
{ | ||
continue; | ||
} | ||
var node = new Orcanode(name.ToString(), nodeName.ToString(), bucket.ToString(), slug.ToString()); | ||
result.NodeList.Add(node); | ||
} | ||
result.Succeeded = true; | ||
} | ||
catch (Exception) | ||
{ | ||
} | ||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// Convert a unix timestamp in integer form to a DateTime value. | ||
/// </summary> | ||
/// <param name="unixTimeStamp">Unix timestamp</param> | ||
/// <returns>DateTime value or null on failure</returns> | ||
public static DateTime? UnixTimeStampToDateTime(long unixTimeStamp) | ||
{ | ||
// A Unix timestamp is a count of seconds past the Unix epoch. | ||
DateTime dateTime = _unixEpoch.AddSeconds(unixTimeStamp); | ||
return dateTime; | ||
} | ||
|
||
/// <summary> | ||
/// Convert a unix timestamp in string form to a DateTime value. | ||
/// </summary> | ||
/// <param name="unixTimeStampString">Unix timestamp string to parse</param> | ||
/// <returns>DateTime value or null on failure</returns> | ||
private static DateTime? UnixTimeStampStringToDateTime(string unixTimeStampString) | ||
{ | ||
if (!long.TryParse(unixTimeStampString, out var unixTimeStamp)) | ||
{ | ||
return null; | ||
} | ||
|
||
return UnixTimeStampToDateTime(unixTimeStamp); | ||
} | ||
|
||
public static long DateTimeToUnixTimeStamp(DateTime dateTime) | ||
{ | ||
DateTime utcTime = dateTime.ToUniversalTime(); | ||
long unixTime = (long)(utcTime - _unixEpoch).TotalSeconds; | ||
return unixTime; | ||
} | ||
|
||
/// <summary> | ||
/// Update the timestamps for a given Orcanode by querying files on S3. | ||
/// </summary> | ||
/// <param name="node">Orcanode to update</param> | ||
/// <returns></returns> | ||
public async static Task UpdateLatestTimestampAsync(Orcanode node) | ||
{ | ||
string url = "https://" + node.Bucket + ".s3.amazonaws.com/" + node.NodeName + "/latest.txt"; | ||
HttpResponseMessage response = await _httpClient.GetAsync(url); | ||
if (!response.IsSuccessStatusCode) | ||
{ | ||
return; | ||
} | ||
|
||
string content = await response.Content.ReadAsStringAsync(); | ||
string unixTimestampString = content.TrimEnd(); | ||
DateTime? latestRecorded = UnixTimeStampStringToDateTime(unixTimestampString); | ||
if (latestRecorded.HasValue) | ||
{ | ||
node.LatestRecorded = latestRecorded; | ||
|
||
DateTimeOffset? offset = response.Content.Headers.LastModified; | ||
if (offset.HasValue) | ||
{ | ||
node.LatestUploaded = offset.Value.UtcDateTime; | ||
} | ||
} | ||
|
||
await UpdateManifestTimestampAsync(node, unixTimestampString); | ||
} | ||
|
||
/// <summary> | ||
/// Update the ManifestUpdated timestamp for a given Orcanode by querying S3. | ||
/// </summary> | ||
/// <param name="node">Orcanode to update</param> | ||
/// <param name="unixTimestampString">Value in the latest.txt file</param> | ||
/// <returns></returns> | ||
public async static Task UpdateManifestTimestampAsync(Orcanode node, string unixTimestampString) | ||
{ | ||
string url = "https://" + node.Bucket + ".s3.amazonaws.com/" + node.NodeName + "/hls/" + unixTimestampString + "/live.m3u8"; | ||
HttpResponseMessage response = await _httpClient.GetAsync(url); | ||
if (!response.IsSuccessStatusCode) | ||
{ | ||
return; | ||
} | ||
|
||
DateTimeOffset? offset = response.Content.Headers.LastModified; | ||
if (offset.HasValue) | ||
{ | ||
node.ManifestUpdated = offset.Value.UtcDateTime; | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.