Add PC uptime tracking feature

- Database: Add lastboottime column to machines table
- API: Accept lastBootUpTime parameter and store in lastboottime column
- PowerShell: Collect LastBootUpTime from Win32_OperatingSystem
  - Update-PC-Minimal.ps1: Add last boot time collection
  - Update-ShopfloorPCs-Remote.ps1: Add last boot time collection and API posting
- Display: Add Uptime column to displaypcs.asp with color-coded badges
  - > 90 days: red badge
  - > 30 days: yellow badge
  - > 7 days: blue badge
  - <= 7 days: muted text
- Filter: Add "Uptime > X days" filter dropdown (7, 30, 90 days)
- SQL: Production migration script for lastboottime column

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
cproudlock
2025-12-09 09:36:23 -05:00
parent 01d4aae38d
commit cd9058d81e
5 changed files with 125 additions and 11 deletions

51
api.asp
View File

@@ -115,6 +115,13 @@ Sub UpdateCompleteAsset()
Dim hasWinRM
hasWinRM = Trim(Request.Form("hasWinRM") & "")
' Last boot time (optional) - accepts both lastBootUpTime and lastBootTime
Dim lastBootTime
lastBootTime = Trim(Request.Form("lastBootUpTime") & "")
If lastBootTime = "" Then
lastBootTime = Trim(Request.Form("lastBootTime") & "")
End If
' DNC/GE registry data
dncDualPathEnabled = Request.Form("dncDualPathEnabled")
dncPath1Name = Trim(Request.Form("dncPath1Name") & "")
@@ -290,7 +297,14 @@ Sub UpdateCompleteAsset()
winrmValue = 0
End If
strSQL = "UPDATE machines SET serialnumber='" & safeSerial & "', modelnumberid=" & modelId & ", machinetypeid=" & machineTypeId & ", osid=" & osid & ", isvnc=" & vncValue & ", iswinrm=" & winrmValue & ", lastupdated=NOW() WHERE machineid=" & machineid
' Build UPDATE with optional lastboottime
Dim lastBootPart
If lastBootTime <> "" Then
lastBootPart = ", lastboottime='" & Replace(lastBootTime, "'", "''") & "'"
Else
lastBootPart = ""
End If
strSQL = "UPDATE machines SET serialnumber='" & safeSerial & "', modelnumberid=" & modelId & ", machinetypeid=" & machineTypeId & ", osid=" & osid & ", isvnc=" & vncValue & ", iswinrm=" & winrmValue & lastBootPart & ", lastupdated=NOW() WHERE machineid=" & machineid
objConn.Execute strSQL
If Err.Number <> 0 Then
SendError debugMsg & "10-UPDATE failed: " & Err.Description
@@ -321,7 +335,16 @@ Sub UpdateCompleteAsset()
winrmValueInsert = 0
End If
strSQL = "INSERT INTO machines (hostname, serialnumber, modelnumberid, machinetypeid, osid, machinestatusid, isvnc, iswinrm, lastupdated) VALUES ('" & safeHostname & "', '" & safeSerial & "', " & modelId & ", " & machineTypeId & ", " & osid & ", " & pcstatusid & ", " & vncValueInsert & ", " & winrmValueInsert & ", NOW())"
' Build INSERT with optional lastboottime
Dim lastBootColInsert, lastBootValInsert
If lastBootTime <> "" Then
lastBootColInsert = ", lastboottime"
lastBootValInsert = ", '" & Replace(lastBootTime, "'", "''") & "'"
Else
lastBootColInsert = ""
lastBootValInsert = ""
End If
strSQL = "INSERT INTO machines (hostname, serialnumber, modelnumberid, machinetypeid, osid, machinestatusid, isvnc, iswinrm, lastupdated" & lastBootColInsert & ") VALUES ('" & safeHostname & "', '" & safeSerial & "', " & modelId & ", " & machineTypeId & ", " & osid & ", " & pcstatusid & ", " & vncValueInsert & ", " & winrmValueInsert & ", NOW()" & lastBootValInsert & ")"
objConn.Execute strSQL
If Err.Number <> 0 Then
SendError debugMsg & "10-INSERT failed: " & Err.Description
@@ -922,6 +945,14 @@ Function InsertOrUpdatePC(conn, hostname, serialnumber, manufacturer, model, pcT
sqlStatusId = "NULL"
End If
' Build lastboottime part for UPDATE
Dim sqlLastBoot
If lastBootTime <> "" Then
sqlLastBoot = "lastboottime = '" & Replace(lastBootTime, "'", "''") & "', "
Else
sqlLastBoot = ""
End If
strSQL = "UPDATE machines SET " & _
"serialnumber = '" & safeSerial & "', " & _
"modelnumberid = " & sqlModelId & ", " & _
@@ -930,6 +961,7 @@ Function InsertOrUpdatePC(conn, hostname, serialnumber, manufacturer, model, pcT
"machinenumber = " & sqlMachineNum & ", " & _
"osid = " & sqlOsId & ", " & _
"machinestatusid = " & sqlStatusId & ", " & _
sqlLastBoot & _
"lastupdated = NOW() " & _
"WHERE machineid = " & CLng(machineid) & " AND pctypeid IS NOT NULL"
@@ -967,7 +999,16 @@ Function InsertOrUpdatePC(conn, hostname, serialnumber, manufacturer, model, pcT
' Build SQL in parts to isolate error
Dim sqlPart1, sqlPart2, sqlPart3
sqlPart1 = "INSERT INTO machines (hostname, serialnumber, modelnumberid, machinetypeid, pctypeid, loggedinuser, machinenumber, osid, machinestatusid, isactive, lastupdated) VALUES ("
' Add lastboottime column if provided
Dim sqlLastBootCol, sqlLastBootVal
If lastBootTime <> "" Then
sqlLastBootCol = ", lastboottime"
sqlLastBootVal = ", '" & Replace(lastBootTime, "'", "''") & "'"
Else
sqlLastBootCol = ""
sqlLastBootVal = ""
End If
sqlPart1 = "INSERT INTO machines (hostname, serialnumber, modelnumberid, machinetypeid, pctypeid, loggedinuser, machinenumber, osid, machinestatusid, isactive, lastupdated" & sqlLastBootCol & ") VALUES ("
sqlPart2 = "'" & safeHostname & "', '" & safeSerial & "', "
If modelId > 0 Then
@@ -998,9 +1039,9 @@ Function InsertOrUpdatePC(conn, hostname, serialnumber, manufacturer, model, pcT
End If
If pcstatusid > 0 Then
sqlPart3 = sqlPart3 & CLng(pcstatusid) & ", 1, NOW())"
sqlPart3 = sqlPart3 & CLng(pcstatusid) & ", 1, NOW()" & sqlLastBootVal & ")"
Else
sqlPart3 = sqlPart3 & "NULL, 1, NOW())"
sqlPart3 = sqlPart3 & "NULL, 1, NOW()" & sqlLastBootVal & ")"
End If
strSQL = sqlPart1 & sqlPart2 & sqlPart3

View File

@@ -39,11 +39,12 @@
</div>
</div>
<%
Dim currentPCStatus, recentFilter, deviceTypeFilter, pcTypeFilter, sel
Dim currentPCStatus, recentFilter, deviceTypeFilter, pcTypeFilter, uptimeFilter, sel
currentPCStatus = Request.QueryString("pcstatus")
recentFilter = Request.QueryString("recent")
deviceTypeFilter = Request.QueryString("devicetype")
pcTypeFilter = Request.QueryString("pctype")
uptimeFilter = Request.QueryString("uptime")
' Check for specialized PCs (CMM, Wax Trace, Measuring Tool) without equipment relationships
Dim rsUnlinked, unlinkedCount
@@ -110,7 +111,13 @@ Set rsStatus = Nothing
<option value="">All Time</option>
<option value="7"<% If recentFilter = "7" Then Response.Write(" selected") End If%>>Last 7 Days</option>
</select>
<% If currentPCStatus <> "" Or recentFilter <> "" Or deviceTypeFilter <> "" Or pcTypeFilter <> "" Or Request.QueryString("needsrelationship") <> "" Then %>
<select id="uptimeFilter" class="btn btn-secondary btn-sm" onchange="updateFilter('uptime', this.value)">
<option value="">All Uptimes</option>
<option value="7"<% If uptimeFilter = "7" Then Response.Write(" selected") End If%>>Uptime > 7 Days</option>
<option value="30"<% If uptimeFilter = "30" Then Response.Write(" selected") End If%>>Uptime > 30 Days</option>
<option value="90"<% If uptimeFilter = "90" Then Response.Write(" selected") End If%>>Uptime > 90 Days</option>
</select>
<% If currentPCStatus <> "" Or recentFilter <> "" Or deviceTypeFilter <> "" Or pcTypeFilter <> "" Or uptimeFilter <> "" Or Request.QueryString("needsrelationship") <> "" Then %>
<a href="displaypcs.asp" class="btn btn-outline-secondary btn-sm">
<i class="zmdi zmdi-close"></i> Clear
</a>
@@ -129,6 +136,7 @@ Set rsStatus = Nothing
<th scope="col">Model</th>
<th scope="col">OS</th>
<th scope="col">Equipment</th>
<th scope="col">Uptime</th>
<th scope="col">VNC</th>
<th scope="col">WinRM</th>
</tr>
@@ -137,17 +145,19 @@ Set rsStatus = Nothing
<%
' Build query based on filters
Dim pcStatusFilter, recentDaysFilter, deviceTypeFilterSQL, pcTypeFilterSQL, needsRelationshipFilter, whereClause
Dim displayName, hasVnc, vncHost, hasWinrm
Dim pcStatusFilter, recentDaysFilter, deviceTypeFilterSQL, pcTypeFilterSQL, uptimeFilterSQL, needsRelationshipFilter, whereClause
Dim displayName, hasVnc, vncHost, hasWinrm, uptimeDays
pcStatusFilter = Request.QueryString("pcstatus")
recentDaysFilter = Request.QueryString("recent")
deviceTypeFilterSQL = Request.QueryString("devicetype")
pcTypeFilterSQL = Request.QueryString("pctype")
uptimeFilterSQL = Request.QueryString("uptime")
needsRelationshipFilter = Request.QueryString("needsrelationship")
' Base query with LEFT JOINs to show all PCs
strSQL = "SELECT m.machineid, m.hostname, m.serialnumber, m.machinenumber, m.machinestatusid, " & _
"m.modelnumberid, m.osid, m.loggedinuser, m.lastupdated, m.isvnc, m.iswinrm, " & _
"m.modelnumberid, m.osid, m.loggedinuser, m.lastupdated, m.isvnc, m.iswinrm, m.lastboottime, " & _
"DATEDIFF(NOW(), m.lastboottime) AS uptime_days, " & _
"vendors.vendor, models.modelnumber, operatingsystems.operatingsystem, " & _
"c.address AS ipaddress, c.macaddress, " & _
"machinestatus.machinestatus, " & _
@@ -184,6 +194,11 @@ Set rsStatus = Nothing
whereClause = whereClause & " AND m.pctypeid = " & pcTypeFilterSQL
End If
' Filter by uptime (days since last boot)
If uptimeFilterSQL <> "" And IsNumeric(uptimeFilterSQL) Then
whereClause = whereClause & " AND m.lastboottime IS NOT NULL AND DATEDIFF(NOW(), m.lastboottime) > " & uptimeFilterSQL
End If
' Filter for specialized PCs needing equipment relationships
If needsRelationshipFilter = "1" Then
whereClause = whereClause & " AND m.pctypeid = 7" & _
@@ -216,6 +231,23 @@ Set rsStatus = Nothing
Response.Write("<span class='text-muted'>-</span>")
End If
%></td>
<td><%
' Uptime column - show days since last boot
If Not IsNull(rs("uptime_days")) And rs("uptime_days") <> "" Then
uptimeDays = CLng(rs("uptime_days") & "")
If uptimeDays > 90 Then
Response.Write("<span class='badge badge-danger' title='Last boot: " & rs("lastboottime") & "'>" & uptimeDays & "d</span>")
ElseIf uptimeDays > 30 Then
Response.Write("<span class='badge badge-warning' title='Last boot: " & rs("lastboottime") & "'>" & uptimeDays & "d</span>")
ElseIf uptimeDays > 7 Then
Response.Write("<span class='badge badge-info' title='Last boot: " & rs("lastboottime") & "'>" & uptimeDays & "d</span>")
Else
Response.Write("<span class='text-muted' title='Last boot: " & rs("lastboottime") & "'>" & uptimeDays & "d</span>")
End If
Else
Response.Write("<span class='text-muted'>-</span>")
End If
%></td>
<td><%
' VNC column with link
hasVnc = False

View File

@@ -81,11 +81,17 @@ if ($interfaces.Count -gt 0) {
$data.networkInterfaces = ($interfaces | ConvertTo-Json -Compress)
}
# Try to get OS
# Try to get OS and last boot time
try {
$os = Get-CimInstance -ClassName Win32_OperatingSystem -ErrorAction Stop
$data.osVersion = $os.Caption
"OS: $($data.osVersion)" | Tee-Object -FilePath $logFile -Append
# Get last boot time
if ($os.LastBootUpTime) {
$data.lastBootUpTime = $os.LastBootUpTime.ToString("yyyy-MM-dd HH:mm:ss")
"Last Boot: $($data.lastBootUpTime)" | Tee-Object -FilePath $logFile -Append
}
} catch {
"ERROR getting OS: $_" | Tee-Object -FilePath $logFile -Append
}

View File

@@ -327,6 +327,7 @@ function Get-RemotePCInfo {
$result.Model = $computerSystem.Model
$result.LoggedInUser = $computerSystem.UserName
$result.OSVersion = $os.Caption
$result.LastBootUpTime = if ($os.LastBootUpTime) { $os.LastBootUpTime.ToString("yyyy-MM-dd HH:mm:ss") } else { $null }
# Get network interfaces
$networkAdapters = Get-CimInstance -ClassName Win32_NetworkAdapterConfiguration |
@@ -692,6 +693,11 @@ function Send-PCDataToApi {
osVersion = $PCData.OSVersion
}
# Add last boot time if available
if ($PCData.LastBootUpTime) {
$postData.lastBootUpTime = $PCData.LastBootUpTime
}
# Add machine number if available
if ($PCData.MachineNo) {
$postData.machineNo = $PCData.MachineNo

View File

@@ -0,0 +1,29 @@
-- Add lastboottime column to machines table for PC uptime tracking
-- Run on production database
-- Date: 2025-12-09
-- Check if column exists before adding
SET @dbname = DATABASE();
SET @tablename = 'machines';
SET @columnname = 'lastboottime';
SET @preparedStatement = (SELECT IF(
(
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = @dbname
AND TABLE_NAME = @tablename
AND COLUMN_NAME = @columnname
) > 0,
'SELECT ''Column lastboottime already exists'';',
'ALTER TABLE machines ADD COLUMN lastboottime DATETIME NULL DEFAULT NULL AFTER lastupdated;'
));
PREPARE alterIfNotExists FROM @preparedStatement;
EXECUTE alterIfNotExists;
DEALLOCATE PREPARE alterIfNotExists;
-- Verify the column was added
SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'machines'
AND COLUMN_NAME = 'lastboottime';