111 lines
5.1 KiB
Plaintext
111 lines
5.1 KiB
Plaintext
@model CalendarSync.Controllers.DashboardViewModel
|
|
@{
|
|
ViewData["Title"] = "Sync Dashboard";
|
|
}
|
|
|
|
<div class="container-fluid mt-4">
|
|
<div class="row mb-4">
|
|
<div class="col">
|
|
<h1 class="display-4 fw-bold text-primary">Sync Transparency Dashboard</h1>
|
|
<p class="lead">Real-time visibility into M365 Webhooks and Delta Syncs.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- User Configs Card -->
|
|
<div class="col-lg-5 mb-4">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header bg-dark text-white fw-bold">User Configurations</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover mb-0">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Email</th>
|
|
<th>Expiration</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var config in Model.Configs)
|
|
{
|
|
<tr>
|
|
<td>@config.Email</td>
|
|
<td>@config.Expiration?.ToString("g")</td>
|
|
<td>
|
|
<span class="badge @(config.Expiration > DateTimeOffset.Now ? "bg-success" : "bg-danger")">
|
|
@(config.Expiration > DateTimeOffset.Now ? "Active" : "Expired")
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Webhook Logs Card -->
|
|
<div class="col-lg-7 mb-4">
|
|
<div class="card shadow-sm border-0 h-100">
|
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
|
<span class="fw-bold">Webhook Transaction Logs</span>
|
|
<span id="sync-status" class="badge bg-light text-primary">SignalR Connected</span>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive" style="max-height: 600px; overflow-y: auto;">
|
|
<table class="table table-sm table-hover mb-0" id="logs-table">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Received At</th>
|
|
<th>Type</th>
|
|
<th>Payload (First 50 chars)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="logs-body">
|
|
@foreach (var log in Model.Logs)
|
|
{
|
|
<tr>
|
|
<td>@log.ReceivedAt.ToString("HH:mm:ss")</td>
|
|
<td><span class="badge @(log.EndpointType == "Listen" ? "bg-info" : "bg-warning") text-dark">@log.EndpointType</span></td>
|
|
<td class="text-truncate" style="max-width: 300px;">@log.RawPayload</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@section Scripts {
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.min.js"></script>
|
|
<script>
|
|
const connection = new signalR.HubConnectionBuilder().withUrl("/syncHub").build();
|
|
|
|
connection.on("ReceiveLog", function (log) {
|
|
const table = document.getElementById("logs-body");
|
|
const row = table.insertRow(0);
|
|
row.className = "table-success animate__animated animate__fadeIn";
|
|
row.innerHTML = `
|
|
<td>${new Date(log.receivedAt).toLocaleTimeString()}</td>
|
|
<td><span class="badge ${log.endpointType === 'Listen' ? 'bg-info' : 'bg-warning'} text-dark">${log.endpointType}</span></td>
|
|
<td class="text-truncate" style="max-width: 300px;">${log.rawPayload}</td>
|
|
`;
|
|
setTimeout(() => row.classList.remove("table-success"), 2000);
|
|
});
|
|
|
|
connection.on("ReceiveStatus", function (status) {
|
|
const statusBadge = document.getElementById("sync-status");
|
|
statusBadge.innerText = status;
|
|
statusBadge.classList.replace("bg-light", "bg-warning");
|
|
setTimeout(() => statusBadge.classList.replace("bg-warning", "bg-light"), 3000);
|
|
});
|
|
|
|
connection.start().catch(err => console.error(err.toString()));
|
|
</script>
|
|
}
|