166 lines
6.5 KiB
Plaintext
166 lines
6.5 KiB
Plaintext
@page "/clusters/{ClusterId}"
|
|
@using Microsoft.AspNetCore.Components.Web
|
|
@using Microsoft.AspNetCore.SignalR.Client
|
|
@using ZB.MOM.WW.OtOpcUa.Admin.Hubs
|
|
@using ZB.MOM.WW.OtOpcUa.Admin.Services
|
|
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
|
|
@using ZB.MOM.WW.OtOpcUa.Configuration.Enums
|
|
@implements IAsyncDisposable
|
|
@rendermode RenderMode.InteractiveServer
|
|
@inject ClusterService ClusterSvc
|
|
@inject GenerationService GenerationSvc
|
|
@inject NavigationManager Nav
|
|
|
|
@if (_cluster is null)
|
|
{
|
|
<p>Loading…</p>
|
|
}
|
|
else
|
|
{
|
|
@if (_liveBanner is not null)
|
|
{
|
|
<div class="alert alert-info py-2 small">
|
|
<strong>Live update:</strong> @_liveBanner
|
|
<button type="button" class="btn-close float-end" @onclick="() => _liveBanner = null"></button>
|
|
</div>
|
|
}
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<div>
|
|
<h1 class="mb-0">@_cluster.Name</h1>
|
|
<code class="text-muted">@_cluster.ClusterId</code>
|
|
@if (!_cluster.Enabled) { <span class="badge bg-secondary ms-2">Disabled</span> }
|
|
</div>
|
|
<div>
|
|
@if (_currentDraft is not null)
|
|
{
|
|
<a href="/clusters/@ClusterId/draft/@_currentDraft.GenerationId" class="btn btn-outline-primary">
|
|
Edit current draft (gen @_currentDraft.GenerationId)
|
|
</a>
|
|
}
|
|
else
|
|
{
|
|
<button class="btn btn-primary" @onclick="CreateDraftAsync" disabled="@_busy">New draft</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<ul class="nav nav-tabs mb-3">
|
|
<li class="nav-item"><button class="nav-link @Tab("overview")" @onclick='() => _tab = "overview"'>Overview</button></li>
|
|
<li class="nav-item"><button class="nav-link @Tab("generations")" @onclick='() => _tab = "generations"'>Generations</button></li>
|
|
<li class="nav-item"><button class="nav-link @Tab("equipment")" @onclick='() => _tab = "equipment"'>Equipment</button></li>
|
|
<li class="nav-item"><button class="nav-link @Tab("uns")" @onclick='() => _tab = "uns"'>UNS Structure</button></li>
|
|
<li class="nav-item"><button class="nav-link @Tab("namespaces")" @onclick='() => _tab = "namespaces"'>Namespaces</button></li>
|
|
<li class="nav-item"><button class="nav-link @Tab("drivers")" @onclick='() => _tab = "drivers"'>Drivers</button></li>
|
|
<li class="nav-item"><button class="nav-link @Tab("acls")" @onclick='() => _tab = "acls"'>ACLs</button></li>
|
|
<li class="nav-item"><button class="nav-link @Tab("audit")" @onclick='() => _tab = "audit"'>Audit</button></li>
|
|
</ul>
|
|
|
|
@if (_tab == "overview")
|
|
{
|
|
<dl class="row">
|
|
<dt class="col-sm-3">Enterprise / Site</dt><dd class="col-sm-9">@_cluster.Enterprise / @_cluster.Site</dd>
|
|
<dt class="col-sm-3">Redundancy</dt><dd class="col-sm-9">@_cluster.RedundancyMode (@_cluster.NodeCount node@(_cluster.NodeCount == 1 ? "" : "s"))</dd>
|
|
<dt class="col-sm-3">Current published</dt>
|
|
<dd class="col-sm-9">
|
|
@if (_currentPublished is not null) { <span>@_currentPublished.GenerationId (@_currentPublished.PublishedAt?.ToString("u"))</span> }
|
|
else { <span class="text-muted">none published yet</span> }
|
|
</dd>
|
|
<dt class="col-sm-3">Created</dt><dd class="col-sm-9">@_cluster.CreatedAt.ToString("u") by @_cluster.CreatedBy</dd>
|
|
</dl>
|
|
}
|
|
else if (_tab == "generations")
|
|
{
|
|
<Generations ClusterId="@ClusterId"/>
|
|
}
|
|
else if (_tab == "equipment" && _currentDraft is not null)
|
|
{
|
|
<EquipmentTab GenerationId="@_currentDraft.GenerationId"/>
|
|
}
|
|
else if (_tab == "uns" && _currentDraft is not null)
|
|
{
|
|
<UnsTab GenerationId="@_currentDraft.GenerationId" ClusterId="@ClusterId"/>
|
|
}
|
|
else if (_tab == "namespaces" && _currentDraft is not null)
|
|
{
|
|
<NamespacesTab GenerationId="@_currentDraft.GenerationId" ClusterId="@ClusterId"/>
|
|
}
|
|
else if (_tab == "drivers" && _currentDraft is not null)
|
|
{
|
|
<DriversTab GenerationId="@_currentDraft.GenerationId" ClusterId="@ClusterId"/>
|
|
}
|
|
else if (_tab == "acls" && _currentDraft is not null)
|
|
{
|
|
<AclsTab GenerationId="@_currentDraft.GenerationId" ClusterId="@ClusterId"/>
|
|
}
|
|
else if (_tab == "audit")
|
|
{
|
|
<AuditTab ClusterId="@ClusterId"/>
|
|
}
|
|
else
|
|
{
|
|
<p class="text-muted">Open a draft to edit this cluster's content.</p>
|
|
}
|
|
}
|
|
|
|
@code {
|
|
[Parameter] public string ClusterId { get; set; } = string.Empty;
|
|
private ServerCluster? _cluster;
|
|
private ConfigGeneration? _currentDraft;
|
|
private ConfigGeneration? _currentPublished;
|
|
private string _tab = "overview";
|
|
private bool _busy;
|
|
private HubConnection? _hub;
|
|
private string? _liveBanner;
|
|
|
|
private string Tab(string key) => _tab == key ? "active" : string.Empty;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadAsync();
|
|
await ConnectHubAsync();
|
|
}
|
|
|
|
private async Task LoadAsync()
|
|
{
|
|
_cluster = await ClusterSvc.FindAsync(ClusterId, CancellationToken.None);
|
|
var gens = await GenerationSvc.ListRecentAsync(ClusterId, 50, CancellationToken.None);
|
|
_currentDraft = gens.FirstOrDefault(g => g.Status == GenerationStatus.Draft);
|
|
_currentPublished = gens.FirstOrDefault(g => g.Status == GenerationStatus.Published);
|
|
}
|
|
|
|
private async Task ConnectHubAsync()
|
|
{
|
|
_hub = new HubConnectionBuilder()
|
|
.WithUrl(Nav.ToAbsoluteUri("/hubs/fleet"))
|
|
.WithAutomaticReconnect()
|
|
.Build();
|
|
|
|
_hub.On<NodeStateChangedMessage>("NodeStateChanged", async msg =>
|
|
{
|
|
if (msg.ClusterId != ClusterId) return;
|
|
_liveBanner = $"Node {msg.NodeId}: {msg.LastAppliedStatus ?? "seen"} at {msg.LastAppliedAt?.ToString("u") ?? msg.LastSeenAt?.ToString("u") ?? "-"}";
|
|
await LoadAsync();
|
|
await InvokeAsync(StateHasChanged);
|
|
});
|
|
|
|
await _hub.StartAsync();
|
|
await _hub.SendAsync("SubscribeCluster", ClusterId);
|
|
}
|
|
|
|
private async Task CreateDraftAsync()
|
|
{
|
|
_busy = true;
|
|
try
|
|
{
|
|
var draft = await GenerationSvc.CreateDraftAsync(ClusterId, createdBy: "admin-ui", CancellationToken.None);
|
|
Nav.NavigateTo($"/clusters/{ClusterId}/draft/{draft.GenerationId}");
|
|
}
|
|
finally { _busy = false; }
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
if (_hub is not null) await _hub.DisposeAsync();
|
|
}
|
|
}
|