Add PC-machine relationships API and report, fix shopfloor dashboard

- Add getPCMachineRelationships API endpoint for PC-to-machine mappings
- Add pcmachinerelationships.asp report page with copy table/CSV/JSON export
- Fix shopfloor dashboard to immediately hide deactivated notifications
- Add Firewall (machinetypeid 46) support to network device pages
- Add model migration warning banner to networkdevices.asp
- Create SQL script for hybrid model/machine type view

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-01-29 16:06:33 -05:00
parent 603de062e5
commit 8945fe2a0a
10 changed files with 487 additions and 24 deletions

69
api.asp
View File

@@ -61,6 +61,8 @@ Select Case action
GetUDCManualTiming() GetUDCManualTiming()
Case "getDeployableApps" Case "getDeployableApps"
GetDeployableApps() GetDeployableApps()
Case "getPCMachineRelationships"
GetPCMachineRelationships()
Case Else Case Else
SendError "Invalid action: " & action SendError "Invalid action: " & action
End Select End Select
@@ -943,6 +945,73 @@ Sub GetShopfloorPCs()
Response.Write "{""success"":true,""count"":" & pcCount & ",""data"":[" & pcList & "]}" Response.Write "{""success"":true,""count"":" & pcCount & ",""data"":[" & pcList & "]}"
End Sub End Sub
Sub GetPCMachineRelationships()
' Returns PCs that have relationships to machines (equipment) with machine numbers
' Used for identifying which PCs control which shop floor machines
On Error Resume Next
Dim rsRel, strSQL, relList, relCount, relData
strSQL = "SELECT " & _
"pc.machineid AS pc_id, " & _
"pc.machinenumber AS hostname, " & _
"c.address AS ip, " & _
"eq.machineid AS machine_id, " & _
"eq.machinenumber AS machine_number, " & _
"v.vendor AS vendor, " & _
"m.modelnumber AS model " & _
"FROM machinerelationships mr " & _
"JOIN machines eq ON mr.machineid = eq.machineid " & _
"JOIN machines pc ON mr.related_machineid = pc.machineid " & _
"LEFT JOIN communications c ON pc.machineid = c.machineid AND c.isprimary = 1 AND c.comstypeid = 1 " & _
"LEFT JOIN models m ON eq.modelnumberid = m.modelnumberid " & _
"LEFT JOIN vendors v ON m.vendorid = v.vendorid " & _
"WHERE mr.isactive = 1 " & _
"AND pc.pctypeid IS NOT NULL " & _
"AND eq.machinenumber IS NOT NULL AND eq.machinenumber != '' " & _
"AND eq.machinenumber REGEXP '^[0-9]{4}$' " & _
"AND eq.machinenumber NOT IN ('0612', '0613', '0614', '0615') " & _
"AND (v.vendor IS NULL OR v.vendor != 'WJDT') " & _
"AND (m.modelnumber IS NULL OR m.modelnumber != 'TBD') " & _
"ORDER BY eq.machinenumber"
Set rsRel = objConn.Execute(strSQL)
If Err.Number <> 0 Then
SendError "Database error: " & Err.Description
Exit Sub
End If
' Build JSON array
relList = ""
relCount = 0
Do While Not rsRel.EOF
If relList <> "" Then relList = relList & ","
relData = "{"
relData = relData & """pc_id"":" & rsRel("pc_id") & ","
relData = relData & """hostname"":""" & EscapeJSON(rsRel("hostname") & "") & ""","
relData = relData & """ip"":""" & EscapeJSON(rsRel("ip") & "") & ""","
relData = relData & """machine_id"":" & rsRel("machine_id") & ","
relData = relData & """machine_number"":""" & EscapeJSON(rsRel("machine_number") & "") & ""","
relData = relData & """vendor"":""" & EscapeJSON(rsRel("vendor") & "") & ""","
relData = relData & """model"":""" & EscapeJSON(rsRel("model") & "") & """"
relData = relData & "}"
relList = relList & relData
relCount = relCount + 1
rsRel.MoveNext
Loop
rsRel.Close
Set rsRel = Nothing
' Send response
Response.Write "{""success"":true,""count"":" & relCount & ",""data"":[" & relList & "]}"
End Sub
Sub GetHighUptimePCs() Sub GetHighUptimePCs()
' Returns list of PCs with uptime >= specified days (for reboot management) ' Returns list of PCs with uptime >= specified days (for reboot management)
On Error Resume Next On Error Resume Next

View File

@@ -8,6 +8,13 @@ Response.AddHeader "Cache-Control", "no-cache, no-store, must-revalidate"
Dim strSQL, jsonOutput, isFirstCurrent, isFirstUpcoming Dim strSQL, jsonOutput, isFirstCurrent, isFirstUpcoming
Dim businessUnitFilter Dim businessUnitFilter
Dim st, et, isCurrent, isResolved, isUpcoming
Dim typeName, empSsoRaw, ssoArr, idx
Dim singleSSO, singleName, singlePicture
Dim empName, empPicture
Dim upTypeName, upEmpSsoRaw, upSsoArr, upIdx
Dim upSingleSSO, upSingleName, upSinglePicture
Dim upEmpName, upEmpPicture
' Get business unit filter from query string ' Get business unit filter from query string
businessUnitFilter = Request.QueryString("businessunit") businessUnitFilter = Request.QueryString("businessunit")
@@ -16,11 +23,12 @@ strSQL = "SELECT n.notificationid, n.notification, n.starttime, n.endtime, " & _
"n.ticketnumber, n.link, n.isactive, n.isshopfloor, n.businessunitid, " & _ "n.ticketnumber, n.link, n.isactive, n.isshopfloor, n.businessunitid, " & _
"n.employeesso, nt.typename, nt.typecolor, bu.businessunit, " & _ "n.employeesso, nt.typename, nt.typecolor, bu.businessunit, " & _
"CASE " & _ "CASE " & _
" WHEN n.starttime <= NOW() AND (n.endtime IS NULL OR n.endtime >= NOW()) THEN 1 " & _ " WHEN n.starttime <= NOW() AND (n.endtime IS NULL OR n.endtime >= NOW()) AND n.isactive = 1 THEN 1 " & _
" WHEN n.endtime IS NOT NULL AND n.endtime < NOW() AND DATE_ADD(n.endtime, INTERVAL 30 MINUTE) >= NOW() THEN 1 " & _ " WHEN n.endtime IS NOT NULL AND n.endtime < NOW() AND DATE_ADD(n.endtime, INTERVAL 30 MINUTE) >= NOW() THEN 1 " & _
" ELSE 0 " & _ " ELSE 0 " & _
"END as is_current, " & _ "END as is_current, " & _
"CASE " & _ "CASE " & _
" WHEN n.isactive = 0 THEN 1 " & _
" WHEN n.endtime IS NOT NULL AND n.endtime < NOW() THEN 1 " & _ " WHEN n.endtime IS NOT NULL AND n.endtime < NOW() THEN 1 " & _
" ELSE 0 " & _ " ELSE 0 " & _
"END as is_resolved, " & _ "END as is_resolved, " & _
@@ -32,10 +40,9 @@ strSQL = "SELECT n.notificationid, n.notification, n.starttime, n.endtime, " & _
"FROM notifications n " & _ "FROM notifications n " & _
"LEFT JOIN notificationtypes nt ON n.notificationtypeid = nt.notificationtypeid " & _ "LEFT JOIN notificationtypes nt ON n.notificationtypeid = nt.notificationtypeid " & _
"LEFT JOIN businessunits bu ON n.businessunitid = bu.businessunitid " & _ "LEFT JOIN businessunits bu ON n.businessunitid = bu.businessunitid " & _
"WHERE n.isshopfloor = 1 AND (" & _ "WHERE n.isshopfloor = 1 AND n.isactive = 1 AND (" & _
" n.isactive = 1 OR " & _ " n.endtime IS NULL OR " & _
" (n.isactive = 0 AND n.endtime IS NOT NULL AND " & _ " DATE_ADD(n.endtime, INTERVAL 30 MINUTE) >= NOW()" & _
" DATE_ADD(n.endtime, INTERVAL 30 MINUTE) >= NOW())" & _
")" ")"
' Add business unit filter ' Add business unit filter
@@ -51,11 +58,10 @@ strSQL = strSQL & " ORDER BY n.notificationid DESC"
Set rs = objConn.Execute(strSQL) Set rs = objConn.Execute(strSQL)
jsonOutput = "{""success"":true,""timestamp"":""" & FormatDateTime(Now(), 2) & " " & FormatDateTime(Now(), 4) & """,""current"":[" jsonOutput = "{""success"":true,""version"":""v2"",""timestamp"":""" & FormatDateTime(Now(), 2) & " " & FormatDateTime(Now(), 4) & """,""current"":["
isFirstCurrent = True isFirstCurrent = True
Do While Not rs.EOF Do While Not rs.EOF
Dim st, et, isCurrent, isResolved
st = rs("starttime") st = rs("starttime")
et = rs("endtime") et = rs("endtime")
isCurrent = rs("is_current") isCurrent = rs("is_current")
@@ -63,17 +69,14 @@ Do While Not rs.EOF
If isCurrent = 1 Then If isCurrent = 1 Then
' Check if this is a Recognition with multiple employees ' Check if this is a Recognition with multiple employees
Dim typeName, empSsoRaw
typeName = rs("typename") & "" typeName = rs("typename") & ""
empSsoRaw = rs("employeesso") & "" empSsoRaw = rs("employeesso") & ""
If LCase(typeName) = "recognition" And Len(empSsoRaw) > 0 And InStr(empSsoRaw, ",") > 0 Then If LCase(typeName) = "recognition" And Len(empSsoRaw) > 0 And InStr(empSsoRaw, ",") > 0 Then
' Split into individual cards for each employee ' Split into individual cards for each employee
Dim ssoArr, idx
ssoArr = Split(empSsoRaw, ",") ssoArr = Split(empSsoRaw, ",")
For idx = 0 To UBound(ssoArr) For idx = 0 To UBound(ssoArr)
Dim singleSSO, singleName, singlePicture
singleSSO = Trim(ssoArr(idx)) singleSSO = Trim(ssoArr(idx))
singleName = LookupSingleEmployeeName(singleSSO) singleName = LookupSingleEmployeeName(singleSSO)
singlePicture = LookupSingleEmployeePicture(singleSSO) singlePicture = LookupSingleEmployeePicture(singleSSO)
@@ -128,7 +131,6 @@ Do While Not rs.EOF
jsonOutput = jsonOutput & """typecolor"":""" & JSEscape(rs("typecolor") & "") & """," jsonOutput = jsonOutput & """typecolor"":""" & JSEscape(rs("typecolor") & "") & ""","
jsonOutput = jsonOutput & """businessunit"":" & StrOrNull(rs("businessunit")) & "," jsonOutput = jsonOutput & """businessunit"":" & StrOrNull(rs("businessunit")) & ","
' Handle employeesso - can be SSO or NAME:customname ' Handle employeesso - can be SSO or NAME:customname
Dim empName, empPicture
If Left(empSsoRaw, 5) = "NAME:" Then If Left(empSsoRaw, 5) = "NAME:" Then
' Custom name - extract name, no picture ' Custom name - extract name, no picture
empName = Mid(empSsoRaw, 6) empName = Mid(empSsoRaw, 6)
@@ -156,24 +158,20 @@ jsonOutput = jsonOutput & "],""upcoming"":["
isFirstUpcoming = True isFirstUpcoming = True
Do While Not rs.EOF Do While Not rs.EOF
Dim isUpcoming
st = rs("starttime") st = rs("starttime")
et = rs("endtime") et = rs("endtime")
isUpcoming = rs("is_upcoming") isUpcoming = rs("is_upcoming")
If isUpcoming = 1 Then If isUpcoming = 1 Then
' Check if this is a Recognition with multiple employees ' Check if this is a Recognition with multiple employees
Dim upTypeName, upEmpSsoRaw
upTypeName = rs("typename") & "" upTypeName = rs("typename") & ""
upEmpSsoRaw = rs("employeesso") & "" upEmpSsoRaw = rs("employeesso") & ""
If LCase(upTypeName) = "recognition" And Len(upEmpSsoRaw) > 0 And InStr(upEmpSsoRaw, ",") > 0 Then If LCase(upTypeName) = "recognition" And Len(upEmpSsoRaw) > 0 And InStr(upEmpSsoRaw, ",") > 0 Then
' Split into individual cards for each employee ' Split into individual cards for each employee
Dim upSsoArr, upIdx
upSsoArr = Split(upEmpSsoRaw, ",") upSsoArr = Split(upEmpSsoRaw, ",")
For upIdx = 0 To UBound(upSsoArr) For upIdx = 0 To UBound(upSsoArr)
Dim upSingleSSO, upSingleName, upSinglePicture
upSingleSSO = Trim(upSsoArr(upIdx)) upSingleSSO = Trim(upSsoArr(upIdx))
upSingleName = LookupSingleEmployeeName(upSingleSSO) upSingleName = LookupSingleEmployeeName(upSingleSSO)
upSinglePicture = LookupSingleEmployeePicture(upSingleSSO) upSinglePicture = LookupSingleEmployeePicture(upSingleSSO)
@@ -216,7 +214,6 @@ Do While Not rs.EOF
jsonOutput = jsonOutput & """typecolor"":""" & JSEscape(rs("typecolor") & "") & """," jsonOutput = jsonOutput & """typecolor"":""" & JSEscape(rs("typecolor") & "") & ""","
jsonOutput = jsonOutput & """businessunit"":" & StrOrNull(rs("businessunit")) & "," jsonOutput = jsonOutput & """businessunit"":" & StrOrNull(rs("businessunit")) & ","
' Handle employeesso - can be SSO or NAME:customname ' Handle employeesso - can be SSO or NAME:customname
Dim upEmpName, upEmpPicture
If Left(upEmpSsoRaw, 5) = "NAME:" Then If Left(upEmpSsoRaw, 5) = "NAME:" Then
' Custom name - extract name, no picture ' Custom name - extract name, no picture
upEmpName = Mid(upEmpSsoRaw, 6) upEmpName = Mid(upEmpSsoRaw, 6)

View File

@@ -240,7 +240,10 @@
strSQL2 = "SELECT machinetypeid, machinetype FROM machinetypes WHERE isactive = 1 ORDER BY machinetype ASC" strSQL2 = "SELECT machinetypeid, machinetype FROM machinetypes WHERE isactive = 1 ORDER BY machinetype ASC"
Set rsMachineTypes = objConn.Execute(strSQL2) Set rsMachineTypes = objConn.Execute(strSQL2)
While Not rsMachineTypes.EOF While Not rsMachineTypes.EOF
Response.Write("<option value='" & rsMachineTypes("machinetypeid") & "'>" & Server.HTMLEncode(rsMachineTypes("machinetype")) & "</option>") ' Pre-select Firewall (46) as the default for this form
Dim mtSelected
If CStr(rsMachineTypes("machinetypeid")) = "46" Then mtSelected = " selected" Else mtSelected = ""
Response.Write("<option value='" & rsMachineTypes("machinetypeid") & "'" & mtSelected & ">" & Server.HTMLEncode(rsMachineTypes("machinetype")) & "</option>")
rsMachineTypes.MoveNext rsMachineTypes.MoveNext
Wend Wend
rsMachineTypes.Close rsMachineTypes.Close

View File

@@ -297,7 +297,7 @@ strSQL = "SELECT m.machineid, m.machinenumber, m.alias, m.serialnumber, " &_
"AND m.isactive = 1 " &_ "AND m.isactive = 1 " &_
"AND m.mapleft IS NOT NULL " &_ "AND m.mapleft IS NOT NULL " &_
"AND m.maptop IS NOT NULL " &_ "AND m.maptop IS NOT NULL " &_
"AND mt.machinetypeid NOT IN (1, 16, 17, 18, 19, 20) " &_ "AND mt.machinetypeid NOT IN (1, 16, 17, 18, 19, 20, 46) " &_
"AND mt.machinetypeid < 33 " &_ "AND mt.machinetypeid < 33 " &_
"ORDER BY mt.machinetype, m.machinenumber ASC" "ORDER BY mt.machinetype, m.machinenumber ASC"

View File

@@ -262,7 +262,7 @@ Set rsM = objConn.Execute("SELECT m.machineid, m.machinenumber, m.alias, m.maple
"LEFT JOIN machinetypes mt ON mo.machinetypeid = mt.machinetypeid " &_ "LEFT JOIN machinetypes mt ON mo.machinetypeid = mt.machinetypeid " &_
"WHERE m.pctypeid IS NULL " &_ "WHERE m.pctypeid IS NULL " &_
"AND m.isactive = 1 " &_ "AND m.isactive = 1 " &_
"AND mt.machinetypeid NOT IN (1, 16, 17, 18, 19, 20) " &_ "AND mt.machinetypeid NOT IN (1, 16, 17, 18, 19, 20, 46) " &_
"AND mt.machinetypeid < 33 " &_ "AND mt.machinetypeid < 33 " &_
"ORDER BY m.machinenumber") "ORDER BY m.machinenumber")
Do While Not rsM.EOF Do While Not rsM.EOF

View File

@@ -40,6 +40,7 @@
<a class="dropdown-item" href="deviceidf.asp?id=0"><i class="zmdi zmdi-city-alt"></i> Add IDF</a> <a class="dropdown-item" href="deviceidf.asp?id=0"><i class="zmdi zmdi-city-alt"></i> Add IDF</a>
<a class="dropdown-item" href="deviceserver.asp?id=0"><i class="zmdi zmdi-storage"></i> Add Server</a> <a class="dropdown-item" href="deviceserver.asp?id=0"><i class="zmdi zmdi-storage"></i> Add Server</a>
<a class="dropdown-item" href="deviceswitch.asp?id=0"><i class="zmdi zmdi-device-hub"></i> Add Switch</a> <a class="dropdown-item" href="deviceswitch.asp?id=0"><i class="zmdi zmdi-device-hub"></i> Add Switch</a>
<a class="dropdown-item" href="devicefirewall.asp?id=0"><i class="zmdi zmdi-shield-security"></i> Add Firewall</a>
<a class="dropdown-item" href="devicecamera.asp?id=0"><i class="zmdi zmdi-videocam"></i> Add Camera</a> <a class="dropdown-item" href="devicecamera.asp?id=0"><i class="zmdi zmdi-videocam"></i> Add Camera</a>
<a class="dropdown-item" href="deviceaccesspoint.asp?id=0"><i class="zmdi zmdi-wifi"></i> Add Access Point</a> <a class="dropdown-item" href="deviceaccesspoint.asp?id=0"><i class="zmdi zmdi-wifi"></i> Add Access Point</a>
<a class="dropdown-item" href="addprinter.asp"><i class="zmdi zmdi-print"></i> Add Printer</a> <a class="dropdown-item" href="addprinter.asp"><i class="zmdi zmdi-print"></i> Add Printer</a>
@@ -47,6 +48,107 @@
</div> </div>
</div> </div>
<!-- Model Migration Warning - Devices needing vendor/model updates -->
<%
' Check for devices that need model fixes
Dim rsModelIssues, modelIssueCount
Dim strModelIssueSQL
strModelIssueSQL = "SELECT " & _
"ma.machineid, " & _
"COALESCE(ma.alias, ma.machinenumber) as device_name, " & _
"mt_machine.machinetype as current_type, " & _
"ma.machinetypeid as machine_typeid, " & _
"mo.modelnumber, " & _
"CASE " & _
" WHEN ma.modelnumberid IS NULL THEN 'No model assigned' " & _
" WHEN mo.machinetypeid IS NULL THEN 'Model has no type set' " & _
" WHEN mo.machinetypeid NOT IN (16,17,18,19,20,46) THEN 'Model has incorrect type' " & _
" ELSE 'OK' " & _
"END as issue " & _
"FROM machines ma " & _
"JOIN machinetypes mt_machine ON ma.machinetypeid = mt_machine.machinetypeid " & _
"LEFT JOIN models mo ON ma.modelnumberid = mo.modelnumberid " & _
"WHERE ma.machinetypeid IN (16,17,18,19,20,46) " & _
"AND ma.isactive = 1 " & _
"AND (ma.modelnumberid IS NULL OR mo.machinetypeid IS NULL OR mo.machinetypeid NOT IN (16,17,18,19,20,46)) " & _
"ORDER BY mt_machine.machinetype, ma.alias"
Set rsModelIssues = objConn.Execute(strModelIssueSQL)
' Count issues
modelIssueCount = 0
If Not rsModelIssues.EOF Then
rsModelIssues.MoveFirst
Do While Not rsModelIssues.EOF
modelIssueCount = modelIssueCount + 1
rsModelIssues.MoveNext
Loop
rsModelIssues.MoveFirst
End If
If modelIssueCount > 0 Then
%>
<div class="alert alert-warning alert-dismissible fade show" role="alert" style="margin-bottom:20px;">
<h5 class="alert-heading"><i class="zmdi zmdi-alert-triangle"></i> Model Migration Required</h5>
<p><strong><%=modelIssueCount%> device(s)</strong> need vendor/model updates to complete the migration from machine-level type assignment to model-level type assignment.</p>
<hr>
<p class="mb-0">
<a class="btn btn-sm btn-warning" data-toggle="collapse" href="#modelIssuesList" role="button">
<i class="zmdi zmdi-eye"></i> Show Devices Needing Updates
</a>
</p>
<div class="collapse mt-3" id="modelIssuesList">
<table class="table table-sm table-bordered" style="background:#fff; color:#333;">
<thead>
<tr>
<th>Device</th>
<th>Type</th>
<th>Current Model</th>
<th>Issue</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<%
Dim editUrl
Do While Not rsModelIssues.EOF
Select Case rsModelIssues("current_type")
Case "IDF"
editUrl = "deviceidf.asp?id=" & rsModelIssues("machineid")
Case "Server"
editUrl = "deviceserver.asp?id=" & rsModelIssues("machineid")
Case "Switch"
editUrl = "deviceswitch.asp?id=" & rsModelIssues("machineid")
Case "Firewall"
editUrl = "devicefirewall.asp?id=" & rsModelIssues("machineid")
Case "Camera"
editUrl = "devicecamera.asp?id=" & rsModelIssues("machineid")
Case "Access Point"
editUrl = "deviceaccesspoint.asp?id=" & rsModelIssues("machineid")
Case Else
editUrl = "displaydevice.asp?id=" & rsModelIssues("machineid")
End Select
%>
<tr>
<td><%=Server.HTMLEncode(rsModelIssues("device_name") & "")%></td>
<td><%=Server.HTMLEncode(rsModelIssues("current_type") & "")%></td>
<td><%If IsNull(rsModelIssues("modelnumber")) Or rsModelIssues("modelnumber") = "" Then Response.Write("<em>None</em>") Else Response.Write(Server.HTMLEncode(rsModelIssues("modelnumber") & ""))%></td>
<td><span class="badge badge-warning"><%=Server.HTMLEncode(rsModelIssues("issue") & "")%></span></td>
<td><a href="<%=editUrl%>" class="btn btn-xs btn-primary"><i class="zmdi zmdi-edit"></i> Edit</a></td>
</tr>
<%
rsModelIssues.MoveNext
Loop
%>
</tbody>
</table>
</div>
</div>
<%
End If
rsModelIssues.Close
Set rsModelIssues = Nothing
%>
<!-- Filter Tabs --> <!-- Filter Tabs -->
<% <%
Dim filterType Dim filterType
@@ -89,6 +191,11 @@
<i class="zmdi zmdi-device-hub"></i> Switches <i class="zmdi zmdi-device-hub"></i> Switches
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link <%If filterType="Firewall" Then Response.Write("active")%>" href="networkdevices.asp?filter=Firewall">
<i class="zmdi zmdi-shield-security"></i> Firewalls
</a>
</li>
</ul> </ul>
<div class="table-responsive"> <div class="table-responsive">
@@ -102,8 +209,8 @@ If filterType = "IDF" Then
Response.Write("<th scope='col'><i class='zmdi zmdi-pin'></i></th>") Response.Write("<th scope='col'><i class='zmdi zmdi-pin'></i></th>")
Response.Write("<th scope='col'>Name</th>") Response.Write("<th scope='col'>Name</th>")
Response.Write("<th scope='col'>Description</th>") Response.Write("<th scope='col'>Description</th>")
ElseIf filterType = "Server" Or filterType = "Switch" Then ElseIf filterType = "Server" Or filterType = "Switch" Or filterType = "Firewall" Then
' Server/Switch columns: Location, Name, Vendor, Model, Serial, IP Address, FQDN, Description ' Server/Switch/Firewall columns: Location, Name, Vendor, Model, Serial, IP Address, FQDN, Description
Response.Write("<th scope='col'><i class='zmdi zmdi-pin'></i></th>") Response.Write("<th scope='col'><i class='zmdi zmdi-pin'></i></th>")
Response.Write("<th scope='col'>Name</th>") Response.Write("<th scope='col'>Name</th>")
Response.Write("<th scope='col'>Vendor</th>") Response.Write("<th scope='col'>Vendor</th>")
@@ -255,7 +362,7 @@ End If
End If End If
Response.Write("</td>") Response.Write("</td>")
ElseIf filterType = "Server" Or filterType = "Switch" Then ElseIf filterType = "Server" Or filterType = "Switch" Or filterType = "Firewall" Then
' Vendor, Model, Serial, IP Address, FQDN, Description, Actions ' Vendor, Model, Serial, IP Address, FQDN, Description, Actions
Response.Write("<td>") Response.Write("<td>")
If Not IsNull(rs("vendor")) Then If Not IsNull(rs("vendor")) Then
@@ -475,6 +582,9 @@ End If
Case "Switch": Case "Switch":
noDataMessage = "No switches found." noDataMessage = "No switches found."
colspanCount = "8" colspanCount = "8"
Case "Firewall":
noDataMessage = "No firewalls found."
colspanCount = "8"
Case "Camera": Case "Camera":
noDataMessage = "No cameras found." noDataMessage = "No cameras found."
colspanCount = "8" colspanCount = "8"

View File

@@ -246,7 +246,7 @@ strSQL = "SELECT printers.printerid AS id, machines.machinenumber AS name, machi
"LEFT JOIN machinetypes mt ON m.machinetypeid = mt.machinetypeid " &_ "LEFT JOIN machinetypes mt ON m.machinetypeid = mt.machinetypeid " &_
"LEFT JOIN vendors v ON mo.vendorid = v.vendorid " &_ "LEFT JOIN vendors v ON mo.vendorid = v.vendorid " &_
"LEFT JOIN communications c ON m.machineid = c.machineid AND c.isprimary = 1 AND c.comstypeid = 1 " &_ "LEFT JOIN communications c ON m.machineid = c.machineid AND c.isprimary = 1 AND c.comstypeid = 1 " &_
"WHERE m.machinetypeid IN (16, 17, 18, 19, 20) " &_ "WHERE m.machinetypeid IN (16, 17, 18, 19, 20, 46) " &_
"AND m.isactive = 1 " &_ "AND m.isactive = 1 " &_
"AND m.mapleft IS NOT NULL " &_ "AND m.mapleft IS NOT NULL " &_
"AND m.maptop IS NOT NULL " &_ "AND m.maptop IS NOT NULL " &_

158
pcmachinerelationships.asp Normal file
View File

@@ -0,0 +1,158 @@
<%@ Language=VBScript %>
<!--#include file="includes/sql.asp"-->
<!DOCTYPE html>
<html>
<head>
<title>PC-Machine Relationships</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.6.0/css/bootstrap.min.css">
<style>
body { padding: 20px; }
table { font-size: 14px; }
.copy-btn { margin-bottom: 15px; }
pre { background: #f8f9fa; padding: 15px; border-radius: 4px; max-height: 400px; overflow: auto; }
</style>
</head>
<body>
<div class="container-fluid">
<h2>PC-Machine Relationships</h2>
<p class="text-muted">PCs with relationships to shop floor machines</p>
<button class="btn btn-primary copy-btn" onclick="copyTable()">Copy Table to Clipboard</button>
<button class="btn btn-secondary copy-btn" onclick="copyCSV()">Copy as CSV</button>
<button class="btn btn-info copy-btn" onclick="copyJSON()">Copy as JSON</button>
<table class="table table-striped table-bordered table-sm" id="dataTable">
<thead class="thead-dark">
<tr>
<th>Machine #</th>
<th>Vendor</th>
<th>Model</th>
<th>PC Hostname</th>
<th>PC IP</th>
</tr>
</thead>
<tbody>
<%
Dim strSQL, rs
strSQL = "SELECT " & _
"pc.machineid AS pc_id, " & _
"pc.machinenumber AS hostname, " & _
"c.address AS ip, " & _
"eq.machineid AS machine_id, " & _
"eq.machinenumber AS machine_number, " & _
"v.vendor AS vendor, " & _
"m.modelnumber AS model " & _
"FROM machinerelationships mr " & _
"JOIN machines eq ON mr.machineid = eq.machineid " & _
"JOIN machines pc ON mr.related_machineid = pc.machineid " & _
"LEFT JOIN communications c ON pc.machineid = c.machineid AND c.isprimary = 1 AND c.comstypeid = 1 " & _
"LEFT JOIN models m ON eq.modelnumberid = m.modelnumberid " & _
"LEFT JOIN vendors v ON m.vendorid = v.vendorid " & _
"WHERE mr.isactive = 1 " & _
"AND pc.pctypeid IS NOT NULL " & _
"AND eq.machinenumber IS NOT NULL AND eq.machinenumber != '' " & _
"AND eq.machinenumber REGEXP '^[0-9]{4}$' " & _
"AND eq.machinenumber NOT IN ('0612', '0613', '0614', '0615') " & _
"AND (v.vendor IS NULL OR v.vendor != 'WJDT') " & _
"AND (m.modelnumber IS NULL OR m.modelnumber != 'TBD') " & _
"ORDER BY eq.machinenumber"
Set rs = objConn.Execute(strSQL)
Dim rowCount
rowCount = 0
Do While Not rs.EOF
rowCount = rowCount + 1
%>
<tr>
<td><%= Server.HTMLEncode(rs("machine_number") & "") %></td>
<td><%= Server.HTMLEncode(rs("vendor") & "") %></td>
<td><%= Server.HTMLEncode(rs("model") & "") %></td>
<td><%= Server.HTMLEncode(rs("hostname") & "") %></td>
<td><%= Server.HTMLEncode(rs("ip") & "") %></td>
</tr>
<%
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
%>
</tbody>
</table>
<p class="text-muted"><%= rowCount %> records found</p>
</div>
<script>
function copyTable() {
var table = document.getElementById('dataTable');
var text = '';
// Get headers
var headers = table.querySelectorAll('thead th');
headers.forEach(function(th, i) {
text += th.innerText + (i < headers.length - 1 ? '\t' : '\n');
});
// Get rows
var rows = table.querySelectorAll('tbody tr');
rows.forEach(function(row) {
var cells = row.querySelectorAll('td');
cells.forEach(function(td, i) {
text += td.innerText + (i < cells.length - 1 ? '\t' : '\n');
});
});
navigator.clipboard.writeText(text).then(function() {
alert('Table copied to clipboard!');
});
}
function copyCSV() {
var table = document.getElementById('dataTable');
var csv = '';
// Get headers
var headers = table.querySelectorAll('thead th');
headers.forEach(function(th, i) {
csv += '"' + th.innerText + '"' + (i < headers.length - 1 ? ',' : '\n');
});
// Get rows
var rows = table.querySelectorAll('tbody tr');
rows.forEach(function(row) {
var cells = row.querySelectorAll('td');
cells.forEach(function(td, i) {
csv += '"' + td.innerText + '"' + (i < cells.length - 1 ? ',' : '\n');
});
});
navigator.clipboard.writeText(csv).then(function() {
alert('CSV copied to clipboard!');
});
}
function copyJSON() {
var table = document.getElementById('dataTable');
var data = [];
var rows = table.querySelectorAll('tbody tr');
rows.forEach(function(row) {
var cells = row.querySelectorAll('td');
data.push({
"Name": cells[0].innerText,
"IpAddress": cells[4].innerText,
"Group": null
});
});
var json = JSON.stringify(data, null, 2);
navigator.clipboard.writeText(json).then(function() {
alert('JSON copied to clipboard!');
});
}
</script>
</body>
</html>

View File

@@ -1186,15 +1186,40 @@
}); });
} }
// Fallback timeout for pendingDataRender
let pendingDataTimeout = null;
// Render events on the page (with smart delay during transitions) // Render events on the page (with smart delay during transitions)
function renderEvents(data) { function renderEvents(data) {
// If any carousel is mid-transition, delay the render // If any carousel is mid-transition, delay the render
if (isTransitioning || isNonIncidentTransitioning || isRecognitionTransitioning) { if (isTransitioning || isNonIncidentTransitioning || isRecognitionTransitioning) {
console.log('Carousel: Transition in progress, delaying render by 800ms'); console.log('Carousel: Transition in progress, delaying render');
pendingDataRender = data; pendingDataRender = data;
// Fallback: if pendingDataRender isn't processed within 1 second, force render
if (pendingDataTimeout) clearTimeout(pendingDataTimeout);
pendingDataTimeout = setTimeout(() => {
if (pendingDataRender) {
console.log('Carousel: Fallback timeout - forcing render of pending data');
// Reset all transition flags in case they're stuck
isTransitioning = false;
isNonIncidentTransitioning = false;
isRecognitionTransitioning = false;
const dataToRender = pendingDataRender;
pendingDataRender = null;
renderEvents(dataToRender);
}
}, 1000);
return; return;
} }
// Clear fallback timeout since we're rendering now
if (pendingDataTimeout) {
clearTimeout(pendingDataTimeout);
pendingDataTimeout = null;
}
const container = document.getElementById('eventsContainer'); const container = document.getElementById('eventsContainer');
let html = ''; let html = '';
@@ -1539,6 +1564,14 @@
currentItem.classList.remove('exit-up'); currentItem.classList.remove('exit-up');
currentItem.classList.add('enter-down'); currentItem.classList.add('enter-down');
isNonIncidentTransitioning = false; isNonIncidentTransitioning = false;
// If there's pending data to render, render it now
if (pendingDataRender) {
console.log('Non-Incident Carousel: Transition complete, rendering pending data');
const dataToRender = pendingDataRender;
pendingDataRender = null;
renderEvents(dataToRender);
}
}, 800); }, 800);
currentNonIncidentIndex = nextIndex; currentNonIncidentIndex = nextIndex;
@@ -1681,6 +1714,14 @@
currentItem.classList.remove('exit-up'); currentItem.classList.remove('exit-up');
currentItem.classList.add('enter-down'); currentItem.classList.add('enter-down');
isRecognitionTransitioning = false; isRecognitionTransitioning = false;
// If there's pending data to render, render it now
if (pendingDataRender) {
console.log('Recognition Carousel: Transition complete, rendering pending data');
const dataToRender = pendingDataRender;
pendingDataRender = null;
renderEvents(dataToRender);
}
}, 800); }, 800);
currentRecognitionIndex = nextIndex; currentRecognitionIndex = nextIndex;
@@ -1853,6 +1894,10 @@
const data = await response.json(); const data = await response.json();
if (data.success) { if (data.success) {
// Debug: log recognition count
const recCount = data.current ? data.current.filter(e => e.typecolor === 'recognition').length : 0;
console.log(`Fetch: Got ${recCount} recognitions, ${data.current ? data.current.length : 0} total current events`);
renderEvents(data); renderEvents(data);
updateLastUpdateTime(); updateLastUpdateTime();
setConnectionStatus(true); setConnectionStatus(true);

View File

@@ -0,0 +1,81 @@
-- ============================================================================
-- FILE: update_network_devices_view.sql
-- PURPOSE: Update vw_network_devices to use model's machinetypeid (with fallback)
-- DATE: 2026-01-29
--
-- DESCRIPTION:
-- This update changes vw_network_devices to prefer model.machinetypeid over
-- machines.machinetypeid (deprecating the latter). Uses a hybrid approach:
-- - If model has a valid network device machinetypeid (16,17,18,19,20,46), use it
-- - Otherwise fall back to machines.machinetypeid for backward compatibility
--
-- Also adds Firewall (machinetypeid=46) to the list of network device types.
-- ============================================================================
CREATE OR REPLACE VIEW vw_network_devices AS
-- Printers (from separate printers table)
SELECT
'Printer' AS device_type,
p.printerid AS device_id,
p.printerwindowsname AS device_name,
p.modelid AS modelid,
m.modelnumber AS modelnumber,
v.vendor AS vendor,
p.serialnumber AS serialnumber,
p.ipaddress AS ipaddress,
NULL AS description,
p.maptop AS maptop,
p.mapleft AS mapleft,
p.isactive AS isactive,
NULL AS idfid,
NULL AS idfname,
NULL AS macaddress,
p.fqdn AS fqdn
FROM printers p
LEFT JOIN models m ON p.modelid = m.modelnumberid
LEFT JOIN vendors v ON m.vendorid = v.vendorid
UNION ALL
-- Network devices from machines table
-- HYBRID: Use model's machinetypeid if valid network device type,
-- otherwise fall back to machine's machinetypeid
SELECT
mt.machinetype AS device_type,
ma.machineid AS device_id,
COALESCE(ma.alias, ma.machinenumber) AS device_name,
ma.modelnumberid AS modelid,
mo.modelnumber AS modelnumber,
ve.vendor AS vendor,
ma.serialnumber AS serialnumber,
c.address AS ipaddress,
ma.machinenotes AS description,
ma.maptop AS maptop,
ma.mapleft AS mapleft,
ma.isactive AS isactive,
NULL AS idfid,
NULL AS idfname,
c.macaddress AS macaddress,
ma.fqdn AS fqdn
FROM machines ma
LEFT JOIN models mo ON ma.modelnumberid = mo.modelnumberid
JOIN machinetypes mt ON mt.machinetypeid = COALESCE(
-- Prefer model's machinetypeid if it's a valid network device type
CASE WHEN mo.machinetypeid IN (16,17,18,19,20,46) THEN mo.machinetypeid ELSE NULL END,
-- Fall back to machine's machinetypeid
ma.machinetypeid
)
LEFT JOIN vendors ve ON mo.vendorid = ve.vendorid
LEFT JOIN communications c ON ma.machineid = c.machineid AND c.isprimary = 1 AND c.comstypeid = 1
WHERE COALESCE(
CASE WHEN mo.machinetypeid IN (16,17,18,19,20,46) THEN mo.machinetypeid ELSE NULL END,
ma.machinetypeid
) IN (16, 17, 18, 19, 20, 46);
-- Network device type IDs:
-- 16 = Access Point
-- 17 = IDF
-- 18 = Camera
-- 19 = Switch
-- 20 = Server
-- 46 = Firewall (newly added)