Add Galaxy repository API and clients
This commit is contained in:
@@ -23,6 +23,9 @@
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="events">Events</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="galaxy">Galaxy</NavLink>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<NavLink class="nav-link" href="settings">Settings</NavLink>
|
||||
</li>
|
||||
|
||||
@@ -29,6 +29,26 @@ else
|
||||
<MetricCard Label="Queue Overflows" Value="@DashboardDisplay.Count(DashboardDisplay.MetricValue(Snapshot, "mxgateway.queues.overflows"))" />
|
||||
</section>
|
||||
|
||||
<section class="dashboard-section">
|
||||
<div class="section-heading d-flex align-items-center gap-2">
|
||||
<h2>Galaxy Repository</h2>
|
||||
<StatusBadge Text="@Snapshot.Galaxy.Status.ToString()" />
|
||||
<NavLink class="ms-auto small" href="galaxy">View browse details →</NavLink>
|
||||
</div>
|
||||
<div class="metric-grid compact">
|
||||
<MetricCard Label="Last Deploy" Value="@DashboardDisplay.DateTime(Snapshot.Galaxy.LastDeployTime)" />
|
||||
<MetricCard Label="Objects" Value="@DashboardDisplay.Count(Snapshot.Galaxy.ObjectCount)" Detail="@($"{Snapshot.Galaxy.AreaCount:N0} areas")" />
|
||||
<MetricCard Label="Attributes" Value="@DashboardDisplay.Count(Snapshot.Galaxy.AttributeCount)" />
|
||||
<MetricCard Label="Historized" Value="@DashboardDisplay.Count(Snapshot.Galaxy.HistorizedAttributeCount)" />
|
||||
<MetricCard Label="Alarms" Value="@DashboardDisplay.Count(Snapshot.Galaxy.AlarmAttributeCount)" />
|
||||
<MetricCard Label="Last Refresh" Value="@DashboardDisplay.DateTime(Snapshot.Galaxy.LastSuccessAt)" Detail="@GalaxyRefreshDetail()" />
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(Snapshot.Galaxy.LastError))
|
||||
{
|
||||
<div class="empty-state mt-2">@Snapshot.Galaxy.LastError</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="dashboard-section">
|
||||
<div class="section-heading">
|
||||
<h2>Recent Faults</h2>
|
||||
@@ -36,3 +56,23 @@ else
|
||||
<FaultList Faults="@Snapshot.Faults" />
|
||||
</section>
|
||||
}
|
||||
|
||||
@code {
|
||||
private string? GalaxyRefreshDetail()
|
||||
{
|
||||
DashboardGalaxySummary galaxy = Snapshot!.Galaxy;
|
||||
if (galaxy.LastQueriedAt is null)
|
||||
{
|
||||
return "never queried";
|
||||
}
|
||||
|
||||
if (galaxy.LastSuccessAt is null)
|
||||
{
|
||||
return "no successful refresh yet";
|
||||
}
|
||||
|
||||
return galaxy.LastQueriedAt > galaxy.LastSuccessAt
|
||||
? $"last attempt {DashboardDisplay.DateTime(galaxy.LastQueriedAt)}"
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
@page "/galaxy"
|
||||
@page "/dashboard/galaxy"
|
||||
@inherits DashboardPageBase
|
||||
|
||||
<PageTitle>Dashboard Galaxy</PageTitle>
|
||||
|
||||
@if (Snapshot is null)
|
||||
{
|
||||
<div class="empty-state">Loading Galaxy summary.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="dashboard-page-header">
|
||||
<div>
|
||||
<h1>Galaxy Repository</h1>
|
||||
<div class="text-secondary">@RefreshHeading()</div>
|
||||
</div>
|
||||
<StatusBadge Text="@Snapshot.Galaxy.Status.ToString()" />
|
||||
</div>
|
||||
|
||||
<section class="metric-grid">
|
||||
<MetricCard Label="Last Deploy" Value="@DashboardDisplay.DateTime(Snapshot.Galaxy.LastDeployTime)" Detail="@DeployAge()" />
|
||||
<MetricCard Label="Objects" Value="@DashboardDisplay.Count(Snapshot.Galaxy.ObjectCount)" Detail="@($"{Snapshot.Galaxy.AreaCount:N0} areas")" />
|
||||
<MetricCard Label="Attributes" Value="@DashboardDisplay.Count(Snapshot.Galaxy.AttributeCount)" Detail="dynamic, deployed" />
|
||||
<MetricCard Label="Historized" Value="@DashboardDisplay.Count(Snapshot.Galaxy.HistorizedAttributeCount)" />
|
||||
<MetricCard Label="Alarms" Value="@DashboardDisplay.Count(Snapshot.Galaxy.AlarmAttributeCount)" />
|
||||
<MetricCard Label="Last Refresh" Value="@DashboardDisplay.DateTime(Snapshot.Galaxy.LastSuccessAt)" Detail="@LastAttemptDetail()" />
|
||||
</section>
|
||||
|
||||
@if (Snapshot.Galaxy.Status == DashboardGalaxyStatus.Unknown)
|
||||
{
|
||||
<section class="dashboard-section">
|
||||
<div class="empty-state">
|
||||
Galaxy summary has not been collected yet. The dashboard refreshes the
|
||||
summary every @RefreshIntervalSeconds() seconds via the
|
||||
<code>GalaxyRepository</code> service.
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Snapshot.Galaxy.LastError))
|
||||
{
|
||||
<section class="dashboard-section">
|
||||
<div class="section-heading">
|
||||
<h2>Last Error</h2>
|
||||
</div>
|
||||
<div class="empty-state">@Snapshot.Galaxy.LastError</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
<section class="dashboard-section">
|
||||
<div class="section-heading">
|
||||
<h2>Object Categories</h2>
|
||||
</div>
|
||||
@if (Snapshot.Galaxy.ObjectCategories.Count == 0)
|
||||
{
|
||||
<div class="empty-state">No deployed objects observed.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm dashboard-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Category</th>
|
||||
<th scope="col">Category ID</th>
|
||||
<th scope="col">Objects</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (DashboardGalaxyCategoryCount row in Snapshot.Galaxy.ObjectCategories)
|
||||
{
|
||||
<tr>
|
||||
<td>@row.CategoryName</td>
|
||||
<td><code>@row.CategoryId</code></td>
|
||||
<td>@DashboardDisplay.Count(row.ObjectCount)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="dashboard-section">
|
||||
<div class="section-heading">
|
||||
<h2>Top Templates</h2>
|
||||
</div>
|
||||
@if (Snapshot.Galaxy.TopTemplates.Count == 0)
|
||||
{
|
||||
<div class="empty-state">No template usage observed.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm dashboard-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Template</th>
|
||||
<th scope="col">Instances</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (DashboardGalaxyTemplateUsage row in Snapshot.Galaxy.TopTemplates)
|
||||
{
|
||||
<tr>
|
||||
<td><code>@row.TemplateName</code></td>
|
||||
<td>@DashboardDisplay.Count(row.InstanceCount)</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="dashboard-section">
|
||||
<div class="section-heading">
|
||||
<h2>Sync Info</h2>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm dashboard-table details-table">
|
||||
<tbody>
|
||||
<tr><th scope="row">Status</th><td><StatusBadge Text="@Snapshot.Galaxy.Status.ToString()" /></td></tr>
|
||||
<tr><th scope="row">Last successful refresh</th><td>@DashboardDisplay.DateTime(Snapshot.Galaxy.LastSuccessAt)</td></tr>
|
||||
<tr><th scope="row">Last attempt</th><td>@DashboardDisplay.DateTime(Snapshot.Galaxy.LastQueriedAt)</td></tr>
|
||||
<tr><th scope="row">Galaxy <code>time_of_last_deploy</code></th><td>@DashboardDisplay.DateTime(Snapshot.Galaxy.LastDeployTime)</td></tr>
|
||||
<tr><th scope="row">Refresh interval</th><td>@RefreshIntervalSeconds() seconds</td></tr>
|
||||
<tr><th scope="row">Connection string</th><td><code>@DashboardDisplay.Text(GalaxyConnectionStringDisplay())</code></td></tr>
|
||||
<tr><th scope="row">Command timeout</th><td>@CommandTimeoutSeconds() seconds</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="text-secondary small mt-2">
|
||||
Browse data is served by the <code>galaxy_repository.v1.GalaxyRepository</code> gRPC
|
||||
service. Clients call <code>DiscoverHierarchy</code> for the full tree and
|
||||
<code>GetLastDeployTime</code> to detect redeployments.
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Inject]
|
||||
private IOptions<MxGateway.Server.Galaxy.GalaxyRepositoryOptions> GalaxyOptions { get; set; } = null!;
|
||||
|
||||
private string RefreshHeading()
|
||||
{
|
||||
DashboardGalaxySummary galaxy = Snapshot!.Galaxy;
|
||||
return galaxy.LastSuccessAt is null
|
||||
? "Awaiting first successful refresh"
|
||||
: $"Refreshed {DashboardDisplay.DateTime(galaxy.LastSuccessAt)}";
|
||||
}
|
||||
|
||||
private string? DeployAge()
|
||||
{
|
||||
DashboardGalaxySummary galaxy = Snapshot!.Galaxy;
|
||||
if (galaxy.LastDeployTime is null || galaxy.LastSuccessAt is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
TimeSpan age = galaxy.LastSuccessAt.Value - galaxy.LastDeployTime.Value;
|
||||
if (age < TimeSpan.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $"{DashboardDisplay.Duration(age)} ago";
|
||||
}
|
||||
|
||||
private string? LastAttemptDetail()
|
||||
{
|
||||
DashboardGalaxySummary galaxy = Snapshot!.Galaxy;
|
||||
if (galaxy.LastQueriedAt is null)
|
||||
{
|
||||
return "never queried";
|
||||
}
|
||||
|
||||
if (galaxy.LastSuccessAt is null)
|
||||
{
|
||||
return "no successful refresh yet";
|
||||
}
|
||||
|
||||
return galaxy.LastQueriedAt > galaxy.LastSuccessAt
|
||||
? $"last attempt {DashboardDisplay.DateTime(galaxy.LastQueriedAt)}"
|
||||
: null;
|
||||
}
|
||||
|
||||
private int RefreshIntervalSeconds() => Math.Max(1, GalaxyOptions.Value.DashboardRefreshIntervalSeconds);
|
||||
|
||||
private int CommandTimeoutSeconds() => GalaxyOptions.Value.CommandTimeoutSeconds;
|
||||
|
||||
private string? GalaxyConnectionStringDisplay() =>
|
||||
DashboardRedactor.Redact(GalaxyOptions.Value.ConnectionString);
|
||||
}
|
||||
@@ -9,7 +9,9 @@
|
||||
"Ready" or "Healthy" => "text-bg-success",
|
||||
"Creating" or "StartingWorker" or "WaitingForPipe" or "InitializingWorker" or "Closing" => "text-bg-info",
|
||||
"Closed" => "text-bg-secondary",
|
||||
"Faulted" => "text-bg-danger",
|
||||
"Stale" => "text-bg-warning",
|
||||
"Faulted" or "Unavailable" => "text-bg-danger",
|
||||
"Unknown" => "text-bg-light text-dark border",
|
||||
_ => "text-bg-light text-dark border"
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user