@model List @{ ViewData["Title"] = "Sync Status"; var nextSync = (DateTimeOffset?)ViewBag.NextSyncUtc; var lastCycle = (DateTimeOffset?)ViewBag.LastCycleStartedUtc; var now = (DateTimeOffset)ViewBag.Now; }

๐Ÿ“Š Sync Status

@if (TempData["SyncNowMessage"] is string msg) {
@msg
} @* โ”€โ”€ Global sync schedule โ”€โ”€ *@

โฑ Sync Schedule

๐Ÿงช Test Excel โšก Sync Now
Server time (UTC): @now.ToString("yyyy-MM-dd HH:mm:ss") UTC
Last sync cycle started: @if (lastCycle.HasValue) { var ago = now - lastCycle.Value; @lastCycle.Value.ToString("yyyy-MM-dd HH:mm:ss") UTC (@FormatTimeSpan(ago) ago) } else { โณ Not yet run (worker starting upโ€ฆ) }
Next sync cycle at: @if (nextSync.HasValue) { var remaining = nextSync.Value - now; if (remaining.TotalSeconds <= 0) { ๐Ÿ”„ Running nowโ€ฆ } else { @nextSync.Value.ToString("yyyy-MM-dd HH:mm:ss") UTC (in @FormatTimeSpan(remaining)) } } else { โณ Worker starting upโ€ฆ }
@* โ”€โ”€ Per-user status table โ”€โ”€ *@

All enrolled users and their current synchronization state.

@if (Model.Count == 0) {

No users enrolled yet.

} else { @foreach (var user in Model) { var hasDelta = !string.IsNullOrEmpty(user.LastDeltaLink); var hasError = !string.IsNullOrEmpty(user.LastSyncError); var statusClass = hasError ? "status-error" : hasDelta ? "status-synced" : "status-pending"; var statusLabel = hasError ? "Error" : hasDelta ? "Synced" : "Pending"; }
Status Email User ID Delta Link Last Sync Last Result
@statusLabel @user.UserEmail @user.UserId[..Math.Min(8, user.UserId.Length)]โ€ฆ @if (hasDelta) { โœ… Stored (@(user.LastDeltaLink!.Length) chars) } else { โณ Initial sync pending } @if (user.LastSyncUtc.HasValue) { var syncAgo = now - user.LastSyncUtc.Value; @user.LastSyncUtc.Value.ToString("HH:mm:ss")
(@FormatTimeSpan(syncAgo) ago)
} else { โ€” }
@if (hasError) { var isTokenError = user.LastSyncError!.Contains("MsalUiRequired") || user.LastSyncError!.Contains("IDW10502") || user.LastSyncError!.Contains("re-authenticate"); if (isTokenError) { ๐Ÿ”‘ Token expired โ€” re-authenticate here } else { โŒ @(user.LastSyncError!.Length > 80 ? user.LastSyncError[..80] + "โ€ฆ" : user.LastSyncError) } } else if (!string.IsNullOrEmpty(user.LastSyncResult)) { โœ… @user.LastSyncResult } else { โ€” }
}

๐Ÿ“ Detailed sync logs are also written to CalendarSync.xlsx in each user's OneDrive root.

@functions { static string FormatTimeSpan(TimeSpan ts) { if (ts.TotalSeconds < 60) return $"{(int)ts.TotalSeconds}s"; if (ts.TotalMinutes < 60) return $"{(int)ts.TotalMinutes}m {ts.Seconds}s"; if (ts.TotalHours < 24) return $"{(int)ts.TotalHours}h {ts.Minutes}m"; return $"{(int)ts.TotalDays}d {ts.Hours}h"; } }