mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
agent:
added boottime functions: fix completedAt UI: fix timeline missing events
This commit is contained in:
@@ -132,8 +132,8 @@ try {
|
||||
$agentConfig = @{
|
||||
apiBaseUrl = $ApiBaseUrl
|
||||
uploadIntervalSeconds = 30
|
||||
cleanupOnExit = $true
|
||||
selfDestructOnComplete = $true
|
||||
cleanupOnExit = $false
|
||||
selfDestructOnComplete = $false
|
||||
keepLogFile = $true
|
||||
imeMatchLogPath = "C:\ProgramData\AutopilotMonitor\Logs\ime_pattern_matches.log"
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
|
||||
EventType = eventType,
|
||||
Severity = severity,
|
||||
Source = "HelloDetector",
|
||||
Phase = EnrollmentPhase.Unknown,
|
||||
Phase = EnrollmentPhase.FinalizingSetup,
|
||||
Message = message,
|
||||
Data = new Dictionary<string, object>
|
||||
{
|
||||
|
||||
@@ -161,6 +161,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
|
||||
{
|
||||
_logger.Info("EnrollmentTracker: collecting device info (at start)");
|
||||
|
||||
CollectBootTime();
|
||||
CollectNetworkAdapters();
|
||||
CollectDnsConfiguration();
|
||||
CollectProxyConfiguration();
|
||||
@@ -184,6 +185,53 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
|
||||
// Add more collectors here as needed
|
||||
}
|
||||
|
||||
private void CollectBootTime()
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = new Dictionary<string, object>();
|
||||
|
||||
// Query WMI for OS boot time
|
||||
using (var searcher = new ManagementObjectSearcher("SELECT LastBootUpTime FROM Win32_OperatingSystem"))
|
||||
{
|
||||
foreach (ManagementObject obj in searcher.Get())
|
||||
{
|
||||
var lastBootUpTimeStr = obj["LastBootUpTime"]?.ToString();
|
||||
if (!string.IsNullOrEmpty(lastBootUpTimeStr))
|
||||
{
|
||||
// WMI time format: yyyyMMddHHmmss.ffffff+/-UUU
|
||||
var bootTime = ManagementDateTimeConverter.ToDateTime(lastBootUpTimeStr);
|
||||
data["bootTime"] = bootTime.ToString("o"); // ISO 8601 format
|
||||
data["bootTimeUtc"] = bootTime.ToUniversalTime().ToString("o");
|
||||
|
||||
// Calculate uptime
|
||||
var uptime = DateTime.Now - bootTime;
|
||||
data["uptimeMinutes"] = (int)uptime.TotalMinutes;
|
||||
data["uptimeHours"] = uptime.TotalHours;
|
||||
|
||||
_logger.Debug($"Boot time: {bootTime:o}, Uptime: {uptime.TotalMinutes:F1} minutes");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.ContainsKey("bootTime"))
|
||||
{
|
||||
data["note"] = "Boot time could not be determined";
|
||||
}
|
||||
|
||||
EmitDeviceInfoEvent("boot_time",
|
||||
data.ContainsKey("bootTime")
|
||||
? $"Last boot: {data["bootTime"]}"
|
||||
: "Boot time unavailable",
|
||||
data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Warning($"EnrollmentTracker: failed to collect boot time: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectNetworkAdapters()
|
||||
{
|
||||
try
|
||||
@@ -790,7 +838,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
|
||||
EventType = "waiting_for_hello",
|
||||
Severity = EventSeverity.Info,
|
||||
Source = "EnrollmentTracker",
|
||||
Phase = EnrollmentPhase.AccountSetup,
|
||||
Phase = EnrollmentPhase.FinalizingSetup,
|
||||
Message = "User apps completed - waiting for Windows Hello provisioning to finish"
|
||||
});
|
||||
|
||||
|
||||
@@ -152,18 +152,19 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
// Update session status based on events
|
||||
if (completionEvent != null)
|
||||
{
|
||||
// Enrollment completed successfully
|
||||
// Enrollment completed successfully - use event timestamp for accurate duration
|
||||
await _storageService.UpdateSessionStatusAsync(
|
||||
request.TenantId,
|
||||
request.SessionId,
|
||||
SessionStatus.Succeeded,
|
||||
completionEvent.Phase
|
||||
completionEvent.Phase,
|
||||
completedAt: completionEvent.Timestamp
|
||||
);
|
||||
_logger.LogInformation($"{sessionPrefix} Status: Succeeded");
|
||||
}
|
||||
else if (failureEvent != null)
|
||||
{
|
||||
// Enrollment failed
|
||||
// Enrollment failed - use event timestamp for accurate duration
|
||||
var failureReason = failureEvent.Data?.ContainsKey("errorCode") == true
|
||||
? $"{failureEvent.Message} ({failureEvent.Data["errorCode"]})"
|
||||
: failureEvent.Message;
|
||||
@@ -173,7 +174,8 @@ namespace AutopilotMonitor.Functions.Functions
|
||||
request.SessionId,
|
||||
SessionStatus.Failed,
|
||||
failureEvent.Phase,
|
||||
failureReason
|
||||
failureReason,
|
||||
completedAt: failureEvent.Timestamp
|
||||
);
|
||||
_logger.LogWarning($"{sessionPrefix} Status: Failed - {failureReason}");
|
||||
}
|
||||
|
||||
@@ -340,7 +340,7 @@ namespace AutopilotMonitor.Functions.Services
|
||||
/// <summary>
|
||||
/// Updates the session status and current phase
|
||||
/// </summary>
|
||||
public async Task<bool> UpdateSessionStatusAsync(string tenantId, string sessionId, SessionStatus status, EnrollmentPhase? currentPhase = null, string? failureReason = null)
|
||||
public async Task<bool> UpdateSessionStatusAsync(string tenantId, string sessionId, SessionStatus status, EnrollmentPhase? currentPhase = null, string? failureReason = null, DateTime? completedAt = null)
|
||||
{
|
||||
SecurityValidator.EnsureValidGuid(tenantId, nameof(tenantId));
|
||||
SecurityValidator.EnsureValidGuid(sessionId, nameof(sessionId));
|
||||
@@ -370,7 +370,8 @@ namespace AutopilotMonitor.Functions.Services
|
||||
// Set completion time if succeeded or failed
|
||||
if (status == SessionStatus.Succeeded || status == SessionStatus.Failed)
|
||||
{
|
||||
session["CompletedAt"] = DateTime.UtcNow;
|
||||
// Use the provided completedAt timestamp (from event) if available, otherwise use current time
|
||||
session["CompletedAt"] = completedAt ?? DateTime.UtcNow;
|
||||
}
|
||||
|
||||
// Set failure reason if failed
|
||||
|
||||
@@ -62,22 +62,24 @@ const V1_PHASES = [
|
||||
{ id: 3, name: "Apps (Device)", shortName: "Apps (Device)" },
|
||||
{ id: 4, name: "Account Setup", shortName: "Account Setup" },
|
||||
{ id: 5, name: "Apps (User)", shortName: "Apps (User)" },
|
||||
{ id: 6, name: "Complete", shortName: "Complete" },
|
||||
{ id: 6, name: "Finalizing Setup", shortName: "Finalizing" },
|
||||
{ id: 7, name: "Complete", shortName: "Complete" },
|
||||
];
|
||||
|
||||
const V2_PHASES = [
|
||||
{ id: 0, name: "Start", shortName: "Start" },
|
||||
{ id: 1, name: "Device Preparation", shortName: "Device Preparation" },
|
||||
{ id: 3, name: "App Installation", shortName: "Apps" },
|
||||
{ id: 6, name: "Complete", shortName: "Complete" },
|
||||
{ id: 6, name: "Finalizing Setup", shortName: "Finalizing" },
|
||||
{ id: 7, name: "Complete", shortName: "Complete" },
|
||||
];
|
||||
|
||||
const V1_PHASE_ORDER = ["Start", "Device Preparation", "Device Setup", "Apps (Device)", "Account Setup", "Apps (User)", "Complete", "Failed"];
|
||||
const V2_PHASE_ORDER = ["Start", "Device Preparation", "App Installation", "Complete", "Failed"];
|
||||
const V1_PHASE_ORDER = ["Start", "Device Preparation", "Device Setup", "Apps (Device)", "Account Setup", "Apps (User)", "Finalizing Setup", "Complete", "Failed"];
|
||||
const V2_PHASE_ORDER = ["Start", "Device Preparation", "App Installation", "Finalizing Setup", "Complete", "Failed"];
|
||||
|
||||
// Lookup by phase number — Phase 3 has different names per enrollment type
|
||||
const V1_PHASE_NAMES: Record<number, string> = { [-1]: "Unknown", 0: "Start", 1: "Device Preparation", 2: "Device Setup", 3: "Apps (Device)", 4: "Account Setup", 5: "Apps (User)", 6: "Complete", 99: "Failed" };
|
||||
const V2_PHASE_NAMES: Record<number, string> = { [-1]: "Unknown", 0: "Start", 1: "Device Preparation", 2: "Device Setup", 3: "App Installation", 4: "Account Setup", 5: "Apps (User)", 6: "Complete", 99: "Failed" };
|
||||
const V1_PHASE_NAMES: Record<number, string> = { [-1]: "Unknown", 0: "Start", 1: "Device Preparation", 2: "Device Setup", 3: "Apps (Device)", 4: "Account Setup", 5: "Apps (User)", 6: "Finalizing Setup", 7: "Complete", 99: "Failed" };
|
||||
const V2_PHASE_NAMES: Record<number, string> = { [-1]: "Unknown", 0: "Start", 1: "Device Preparation", 2: "Device Setup", 3: "App Installation", 4: "Account Setup", 5: "Apps (User)", 6: "Finalizing Setup", 7: "Complete", 99: "Failed" };
|
||||
|
||||
export default function SessionDetailPage() {
|
||||
const params = useParams();
|
||||
@@ -565,7 +567,7 @@ export default function SessionDetailPage() {
|
||||
<h2 className="text-xl font-semibold text-gray-900 mb-6">Enrollment Progress</h2>
|
||||
<PhaseTimeline
|
||||
currentPhase={session.currentPhase}
|
||||
completedPhases={session.status === 'Succeeded' ? [6] : []}
|
||||
completedPhases={session.status === 'Succeeded' ? [7] : []}
|
||||
events={events}
|
||||
sessionStatus={session.status}
|
||||
enrollmentType={session.enrollmentType}
|
||||
@@ -1309,6 +1311,7 @@ function DeviceDetailsCard({ events }: { events: EnrollmentEvent[] }) {
|
||||
};
|
||||
|
||||
const agentStarted = getEventData("agent_started");
|
||||
const bootTime = getEventData("boot_time");
|
||||
const networkAdapters = getEventData("network_adapters");
|
||||
const dnsConfig = getEventData("dns_configuration");
|
||||
const proxyConfig = getEventData("proxy_configuration");
|
||||
@@ -1319,7 +1322,7 @@ function DeviceDetailsCard({ events }: { events: EnrollmentEvent[] }) {
|
||||
const secureBootStatus = getEventData("secureboot_status");
|
||||
|
||||
// Check if we have any device detail events at all
|
||||
const hasData = agentStarted || networkAdapters || dnsConfig || proxyConfig || autopilotProfile ||
|
||||
const hasData = agentStarted || bootTime || networkAdapters || dnsConfig || proxyConfig || autopilotProfile ||
|
||||
aadJoinStatus || imeVersion || bitLockerStatus || secureBootStatus;
|
||||
|
||||
if (!hasData) return null;
|
||||
@@ -1342,10 +1345,11 @@ function DeviceDetailsCard({ events }: { events: EnrollmentEvent[] }) {
|
||||
{expanded && (
|
||||
<div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Boot Time */}
|
||||
{agentStarted?.bootTime && (
|
||||
{(bootTime?.bootTime || agentStarted?.bootTime) && (
|
||||
<DetailSection title="System Boot">
|
||||
<DetailRow label="Boot Time" value={new Date(agentStarted.bootTime).toLocaleString()} />
|
||||
{agentStarted.agentVersion && <DetailRow label="Agent Version" value={agentStarted.agentVersion} />}
|
||||
<DetailRow label="Boot Time" value={new Date(bootTime?.bootTime || agentStarted?.bootTime).toLocaleString()} />
|
||||
{bootTime?.uptimeMinutes && <DetailRow label="Uptime" value={`${Math.floor(bootTime.uptimeMinutes / 60)}h ${bootTime.uptimeMinutes % 60}m`} />}
|
||||
{agentStarted?.agentVersion && <DetailRow label="Agent Version" value={agentStarted.agentVersion} />}
|
||||
</DetailSection>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user