126 lines
5.1 KiB
Plaintext
126 lines
5.1 KiB
Plaintext
@using ScadaLink.CentralUI.Components.Shared
|
|
@using ScadaLink.CentralUI.Services
|
|
@using ScadaLink.Commons.Entities.Audit
|
|
@using ScadaLink.Commons.Types.Audit
|
|
@using ScadaLink.Commons.Types.Enums
|
|
@inject IAuditLogQueryService QueryService
|
|
|
|
<div data-test="audit-results-grid">
|
|
@if (_error is not null)
|
|
{
|
|
<div class="alert alert-danger small mb-2">@_error</div>
|
|
}
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-sm table-hover align-middle" @ref="_tableRef">
|
|
<thead class="table-light">
|
|
<tr>
|
|
@foreach (var col in OrderedColumns())
|
|
{
|
|
// @key keeps Blazor reusing one DOM node per column across
|
|
// re-renders (reorder/resize), so audit-grid.js binds drag
|
|
// listeners exactly once per <th> and never leaks them onto
|
|
// discarded nodes — the __auditGridCellBound guard relies on
|
|
// this node stability to be fully sound.
|
|
<th class="audit-grid-th"
|
|
@key="col.Key"
|
|
data-test="col-header-@col.Key"
|
|
data-col-key="@col.Key"
|
|
style="@ColumnWidthStyle(col.Key)">
|
|
@col.Label
|
|
<span class="audit-grid-resize-handle"
|
|
data-test="col-resize-@col.Key"
|
|
aria-hidden="true"></span>
|
|
</th>
|
|
}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@if (_rows.Count == 0)
|
|
{
|
|
<tr>
|
|
<td colspan="@OrderedColumns().Count" class="text-muted small text-center py-4">
|
|
@if (_loading)
|
|
{
|
|
<span>Loading…</span>
|
|
}
|
|
else
|
|
{
|
|
<span>No audit events match the current filter.</span>
|
|
}
|
|
</td>
|
|
</tr>
|
|
}
|
|
else
|
|
{
|
|
@foreach (var row in _rows)
|
|
{
|
|
<tr @key="row.EventId"
|
|
data-test="grid-row-@row.EventId"
|
|
class="audit-row"
|
|
style="cursor: pointer;"
|
|
@onclick="() => HandleRowClick(row)">
|
|
@foreach (var col in OrderedColumns())
|
|
{
|
|
<td class="audit-grid-td" style="@ColumnWidthStyle(col.Key)">
|
|
@RenderCell(col.Key, row)
|
|
</td>
|
|
}
|
|
</tr>
|
|
}
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<span class="text-muted small">Page @_pageNumber · @_rows.Count rows</span>
|
|
<button class="btn btn-outline-secondary btn-sm"
|
|
data-test="grid-next-page"
|
|
disabled="@(_loading || _rows.Count < _pageSize)"
|
|
@onclick="NextPage">Next page</button>
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
private RenderFragment RenderCell(string key, AuditEvent row) => __builder =>
|
|
{
|
|
switch (key)
|
|
{
|
|
case "OccurredAtUtc":
|
|
var occurredOffset = new DateTimeOffset(DateTime.SpecifyKind(row.OccurredAtUtc, DateTimeKind.Utc));
|
|
<span title="@row.OccurredAtUtc.ToString("u")">
|
|
<TimestampDisplay Value="occurredOffset" Format="yyyy-MM-dd HH:mm:ss" />
|
|
</span>
|
|
break;
|
|
case "Site":
|
|
<span class="small">@(row.SourceSiteId ?? "—")</span>
|
|
break;
|
|
case "Channel":
|
|
<span class="small">@row.Channel</span>
|
|
break;
|
|
case "Kind":
|
|
<span class="small">@row.Kind</span>
|
|
break;
|
|
case "Status":
|
|
<span data-test="status-badge-@row.EventId" class="badge @StatusBadgeClass(row.Status)">@row.Status</span>
|
|
break;
|
|
case "Target":
|
|
<span class="small">@(row.Target ?? "—")</span>
|
|
break;
|
|
case "Actor":
|
|
<span class="small">@(row.Actor ?? "—")</span>
|
|
break;
|
|
case "DurationMs":
|
|
<span class="small font-monospace">@(row.DurationMs?.ToString() ?? "—")</span>
|
|
break;
|
|
case "HttpStatus":
|
|
<span class="small font-monospace">@(row.HttpStatus?.ToString() ?? "—")</span>
|
|
break;
|
|
case "ErrorMessage":
|
|
<span class="small text-danger" title="@row.ErrorMessage">@TruncateError(row.ErrorMessage)</span>
|
|
break;
|
|
}
|
|
};
|
|
}
|