Fix dualpath propagation, getShopfloorPCs filtering, USB management, and printer features

- Fix dualpath PC propagation direction (Equipment->PC) in api.asp and db_helpers.asp
- Fix early exit in CreatePCMachineRelationship preventing propagation
- Fix getShopfloorPCs to filter machinetypeid IN (33,34,35) instead of >= 33
- Fix getShopfloorPCs to show equipment numbers via GROUP_CONCAT subquery
- Add detailed PropagateDP logging for dualpath debugging
- Default "Show on Shopfloor Dashboard" checkbox to checked in addnotification.asp
- Add USB label batch printing, single USB labels, and USB history pages
- Add printer supplies tracking and toner report enhancements
- Add uptime map visualization page
- Add dashboard/lobby display SQL migration
- Update CLAUDE.md with IIS 401 workaround documentation
- Update TODO.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-02-03 10:44:55 -05:00
parent 8945fe2a0a
commit e382a3246e
27 changed files with 1926 additions and 255 deletions

View File

@@ -3,6 +3,7 @@
<head>
<!--#include file="./includes/header.asp"-->
<!--#include file="./includes/sql.asp"-->
<!--#include file="./includes/printer_supplies.asp"-->
<!--#include file="./includes/zabbix_all_supplies_cached.asp"-->
</head>
@@ -206,13 +207,70 @@
' Xerox VersaLink B7025/B7030/B7035 Monochrome (WorkCentre 7800 series)
ElseIf oem = "006R01756" Then
marketing = "Xerox Black Toner"
' Xerox VersaLink C415
ElseIf oem = "006R04677" Then
marketing = "C415 Black Toner"
ElseIf oem = "006R04678" Then
marketing = "C415 Cyan Toner"
ElseIf oem = "006R04679" Then
marketing = "C415 Magenta Toner"
ElseIf oem = "006R04680" Then
marketing = "C415 Yellow Toner"
ElseIf oem = "006R04681" Then
marketing = "C415 Black Toner (Metered)"
ElseIf oem = "006R04682" Then
marketing = "C415 Cyan Toner (Metered)"
ElseIf oem = "006R04683" Then
marketing = "C415 Magenta Toner (Metered)"
ElseIf oem = "006R04684" Then
marketing = "C415 Yellow Toner (Metered)"
ElseIf oem = "008R13335" Then
marketing = "C415 Waste Cartridge"
ElseIf oem = "013R00701" Then
marketing = "C415 Drum/Imaging Unit"
' Xerox VersaLink C405
ElseIf oem = "106R03500" Then
marketing = "C405 Black Toner"
ElseIf oem = "106R03501" Then
marketing = "C405 Cyan Toner"
ElseIf oem = "106R03502" Then
marketing = "C405 Magenta Toner"
ElseIf oem = "106R03503" Then
marketing = "C405 Yellow Toner"
ElseIf oem = "108R01124" Then
marketing = "C405 Waste Container"
ElseIf oem = "101R00555" Then
marketing = "C405 Drum Cartridge"
' Xerox VersaLink C7125/C7100
ElseIf oem = "006R01824" Then
marketing = "C7125 Black Toner"
ElseIf oem = "006R01825" Then
marketing = "C7125 Cyan Toner"
ElseIf oem = "006R01826" Then
marketing = "C7125 Magenta Toner"
ElseIf oem = "006R01827" Then
marketing = "C7125 Yellow Toner"
ElseIf oem = "006R01820" Then
marketing = "C7125 Black Toner (Metered)"
ElseIf oem = "006R01821" Then
marketing = "C7125 Cyan Toner (Metered)"
ElseIf oem = "006R01822" Then
marketing = "C7125 Magenta Toner (Metered)"
ElseIf oem = "006R01823" Then
marketing = "C7125 Yellow Toner (Metered)"
ElseIf oem = "115R00129" Then
marketing = "VersaLink Waste Container"
ElseIf oem = "013R00688" Then
marketing = "C7125 Drum Cartridge"
' Xerox VersaLink B7125/B7130/B7135 Monochrome
ElseIf oem = "006R01817" Then
marketing = "Xerox Black Toner (Standard)"
marketing = "B7125 Black Toner (Standard)"
ElseIf oem = "006R01818" Then
marketing = "Xerox Black Toner (High Capacity)"
marketing = "B7125 Black Toner (High Capacity)"
ElseIf oem = "006R01819" Then
marketing = "Xerox Black Toner (DMO)"
marketing = "B7125 Black Toner (Metered)"
ElseIf oem = "013R00687" Then
marketing = "B7125 Drum Cartridge"
' Xerox VersaLink C7000 series
ElseIf oem = "106R03536" Then
marketing = "Xerox Black Toner"
@@ -252,15 +310,36 @@
' Xerox Phaser 4600/4620 series
ElseIf oem = "006R01817" Then
marketing = "Xerox Black Toner"
' Xerox (legacy - keeping for compatibility)
' Xerox AltaLink EC8036/C8036 - WC7800 series compatible
ElseIf oem = "006R01509" Then
marketing = "WC7800 Black"
ElseIf oem = "006R01512" Then
marketing = "WC7800 Cyan"
ElseIf oem = "006R01511" Then
marketing = "WC7800 Magenta"
ElseIf oem = "006R01510" Then
marketing = "WC7800 Yellow"
' Xerox AltaLink EC8036/C8036 - AltaLink C80xx series
ElseIf oem = "006R01697" Then
marketing = "Xerox Black Toner"
marketing = "AltaLink Black"
ElseIf oem = "006R01698" Then
marketing = "Xerox Cyan Toner"
marketing = "AltaLink Cyan"
ElseIf oem = "006R01699" Then
marketing = "Xerox Yellow Toner"
marketing = "AltaLink Magenta"
ElseIf oem = "006R01700" Then
marketing = "Xerox Magenta Toner"
marketing = "AltaLink Yellow"
ElseIf oem = "006R01701" Then
marketing = "AltaLink Black (Met)"
ElseIf oem = "006R01702" Then
marketing = "AltaLink Cyan (Met)"
ElseIf oem = "006R01703" Then
marketing = "AltaLink Magenta (Met)"
ElseIf oem = "006R01704" Then
marketing = "AltaLink Yellow (Met)"
ElseIf oem = "008R13061" Then
marketing = "EC8036 Waste Container"
ElseIf oem = "013R00677" Then
marketing = "EC8036 Drum Cartridge"
Else
marketing = "" ' No mapping found - will display OEM number only
@@ -290,7 +369,7 @@
<div>
<h5 class="card-title"><i class='zmdi zmdi-collection-image text-yellow'></i>&nbsp;&nbsp;Supplies Alert Report</h5>
<p class="text-muted" style="font-size:13px; margin-top:5px; margin-bottom:0;">
Monitors: Toner/Ink &le;5%, Drums &le;5%, Maintenance Kits &le;5%, Waste &ge;95% (Xerox EC series: &le;5% inverted)
Monitors: Toner/Ink &le;5%, Drums &le;5%, Maintenance Kits &le;5%, Waste &ge;95% (Xerox: &le;5% inverted)
</p>
</div>
<div style="display:flex; gap:10px; align-items:center;">
@@ -340,6 +419,7 @@
Dim typeMatches, colorMatches
Dim urgencyScore, alertItem, alertItems(1000), alertCount, i, j, tempAlert, k, outputItem
Dim isXeroxPrinter
Dim stdPartNumber, metPartNumber, altPartNumber, stdMarketing, metMarketing, altMarketing, partNumberResult, partNumberParts
lowSuppliesFound = False
alertCount = 0
@@ -364,9 +444,10 @@
machineid = rs("machineid")
vendor = rs("vendor")
' Detect if this is a Xerox EC series printer (EC8036, etc.) for vendor-specific logic
' These enterprise models report waste cartridges inverted from standard behavior
isXeroxPrinter = (InStr(1, vendor, "Xerox", 1) > 0 And InStr(1, modelnumber, "EC", 1) > 0)
' Detect if this is a Xerox printer for vendor-specific waste cartridge logic
' Xerox printers (VersaLink, AltaLink, EC series) report waste inverted:
' 100% = empty (capacity remaining), 0% = full (no capacity remaining)
isXeroxPrinter = (InStr(1, vendor, "Xerox", 1) > 0)
' Use alias if available, otherwise machinenumber
If NOT IsNull(rs("alias")) AND rs("alias") <> "" Then
@@ -617,137 +698,66 @@
End If
End If
' Look up part number for this item
partNumber = "-"
If partNumbers.Count > 0 Then
' Extract base name for lookup - remove " Level" suffix
lookupName = Replace(itemName, " Level", "")
lookupName = Trim(lookupName)
' Look up part numbers from hardcoded list (SNMP is unreliable)
' Returns "standard|metered" or "standard|alt|metered" or just "standard"
partNumberResult = GetSupplyPartNumbers(modelnumber, itemName)
stdPartNumber = ""
metPartNumber = ""
altPartNumber = ""
' Comprehensive matching strategy for all template versions
foundMatch = False
' Strategy 1: EXACT match - NEW template format (preferred)
' "Black Toner Level" → "Black Toner Part Number"
' "Cyan Ink Level" → "Cyan Ink Part Number"
' "Black Drum Level" → "Black Drum Part Number"
partKeyName = lookupName & " Part Number"
If partNumbers.Exists(partKeyName) Then
partNumber = partNumbers(partKeyName)
foundMatch = True
End If
' Strategy 2: Add " Cartridge" - OLD Xerox template format
' "Black Drum Level" → "Black Drum Cartridge Part Number"
' "Black Toner Level" → "Black Toner Cartridge Part Number"
If Not foundMatch Then
tryName = lookupName & " Cartridge Part Number"
If partNumbers.Exists(tryName) Then
partNumber = partNumbers(tryName)
foundMatch = True
End If
End If
' Strategy 3: Replace supply type with "Cartridge" - OLD HP template format
' "Black Toner Level" → "Black Cartridge Part Number"
' "Cyan Ink Level" → "Cyan Cartridge Part Number"
If Not foundMatch Then
' Replace common supply types with "Cartridge"
If InStr(1, lookupName, "Toner", 1) > 0 Then
tryName = Replace(lookupName, "Toner", "Cartridge", 1, -1, 1) & " Part Number"
ElseIf InStr(1, lookupName, "Ink", 1) > 0 Then
tryName = Replace(lookupName, "Ink", "Cartridge", 1, -1, 1) & " Part Number"
ElseIf InStr(1, lookupName, "Drum", 1) > 0 Then
tryName = Replace(lookupName, "Drum", "Cartridge", 1, -1, 1) & " Part Number"
Else
tryName = ""
End If
If tryName <> "" And partNumbers.Exists(tryName) Then
partNumber = partNumbers(tryName)
foundMatch = True
End If
End If
' Strategy 4: Check for "Standard MIB" suffix variation
' "Maintenance Kit Level" → "Maintenance Kit Part Number (Standard MIB)"
If Not foundMatch Then
tryName = lookupName & " Part Number (Standard MIB)"
If partNumbers.Exists(tryName) Then
partNumber = partNumbers(tryName)
foundMatch = True
End If
End If
' Strategy 5: Intelligent fuzzy match by type and color
If Not foundMatch Then
' Extract primary identifier (first significant word)
primaryWord = ""
supplyType = ""
' Determine supply type
If InStr(1, lookupName, "Toner", 1) > 0 Then
supplyType = "Toner"
ElseIf InStr(1, lookupName, "Ink", 1) > 0 Then
supplyType = "Ink"
ElseIf InStr(1, lookupName, "Drum", 1) > 0 Then
supplyType = "Drum"
ElseIf InStr(1, lookupName, "Waste", 1) > 0 Then
supplyType = "Waste"
ElseIf InStr(1, lookupName, "Fuser", 1) > 0 Then
supplyType = "Fuser"
ElseIf InStr(1, lookupName, "Maintenance", 1) > 0 Then
supplyType = "Maintenance"
End If
' Extract color/identifier (first word before supply type)
If supplyType <> "" Then
colorPos = InStr(1, lookupName, supplyType, 1)
If colorPos > 1 Then
primaryWord = Trim(Left(lookupName, colorPos - 1))
End If
End If
' Search all keys for matching type and color
For Each partKey In partNumbers.Keys
If InStr(1, partKey, "Part Number", 1) > 0 Then
' Must match supply type
typeMatches = False
If supplyType <> "" Then
typeMatches = (InStr(1, partKey, supplyType, 1) > 0) Or (InStr(1, partKey, "Cartridge", 1) > 0)
Else
' For items without obvious type, just look for any match
typeMatches = True
End If
' Must match color/identifier if present
colorMatches = True
If primaryWord <> "" Then
colorMatches = (InStr(1, partKey, primaryWord, 1) > 0)
End If
If typeMatches And colorMatches Then
partNumber = partNumbers(partKey)
foundMatch = True
Exit For
End If
End If
Next
If InStr(partNumberResult, "|") > 0 Then
partNumberParts = Split(partNumberResult, "|")
stdPartNumber = partNumberParts(0)
If UBound(partNumberParts) >= 2 Then
' 3 options: std|alt|met
altPartNumber = partNumberParts(1)
metPartNumber = partNumberParts(2)
Else
' 2 options: std|met
metPartNumber = partNumberParts(1)
End If
Else
stdPartNumber = partNumberResult
End If
' Replace generic Xerox part numbers with actual model-specific part numbers
partNumber = GetActualPartNumber(partNumber, modelnumber, itemName)
If stdPartNumber = "" Then stdPartNumber = "-"
' Get marketing name for this part number
marketingName = GetMarketingName(partNumber)
' Get marketing names
stdMarketing = GetMarketingName(stdPartNumber)
metMarketing = ""
altMarketing = ""
If metPartNumber <> "" Then metMarketing = GetMarketingName(metPartNumber)
If altPartNumber <> "" Then altMarketing = GetMarketingName(altPartNumber)
If marketingName <> "" Then
' Show marketing name prominently with OEM number in smaller text
displayPartNumber = "<strong>" & Server.HTMLEncode(marketingName) & "</strong><br><small style='color:#999;'>" & Server.HTMLEncode(partNumber) & "</small>"
' Build display string showing all options
If altPartNumber <> "" Then
' 3 options (EC8036 style)
displayPartNumber = "<div style='line-height:1.3; font-size:12px;'>"
displayPartNumber = displayPartNumber & "<strong>WC:</strong> " & Server.HTMLEncode(stdPartNumber) & "<br>"
displayPartNumber = displayPartNumber & "<strong>Alt:</strong> " & Server.HTMLEncode(altPartNumber) & "<br>"
displayPartNumber = displayPartNumber & "<strong>Met:</strong> " & Server.HTMLEncode(metPartNumber)
displayPartNumber = displayPartNumber & "</div>"
ElseIf metPartNumber <> "" Then
' 2 options
displayPartNumber = "<div style='line-height:1.4;'>"
If stdMarketing <> "" Then
displayPartNumber = displayPartNumber & "<strong>Std:</strong> " & Server.HTMLEncode(stdMarketing) & " <small style='color:#999;'>(" & Server.HTMLEncode(stdPartNumber) & ")</small><br>"
Else
displayPartNumber = displayPartNumber & "<strong>Std:</strong> " & Server.HTMLEncode(stdPartNumber) & "<br>"
End If
If metMarketing <> "" Then
displayPartNumber = displayPartNumber & "<strong>Met:</strong> " & Server.HTMLEncode(metMarketing) & " <small style='color:#999;'>(" & Server.HTMLEncode(metPartNumber) & ")</small>"
Else
displayPartNumber = displayPartNumber & "<strong>Met:</strong> " & Server.HTMLEncode(metPartNumber)
End If
displayPartNumber = displayPartNumber & "</div>"
Else
' No mapping found, just show OEM number
displayPartNumber = Server.HTMLEncode(partNumber)
' Single option
If stdMarketing <> "" Then
displayPartNumber = "<strong>" & Server.HTMLEncode(stdMarketing) & "</strong><br><small style='color:#999;'>" & Server.HTMLEncode(stdPartNumber) & "</small>"
Else
displayPartNumber = Server.HTMLEncode(stdPartNumber)
End If
End If
' Calculate urgency score for sorting
@@ -851,7 +861,7 @@
</div>
<div class="card-footer">
<small class="text-muted">
<i class="zmdi zmdi-info-outline"></i> This report shows printers with low supplies (&le;5%) or waste cartridges near full (&ge;95%, Xerox EC series inverted &le;5%).
<i class="zmdi zmdi-info-outline"></i> This report shows printers with low supplies (&le;5%) or waste cartridges near full (&ge;95%, Xerox inverted &le;5%).
Data refreshed from Zabbix every 5 minutes.
</small>
</div>
@@ -919,6 +929,77 @@
});
</script>
<!-- Print styles for readable printouts -->
<style>
@media print {
/* Reset dark theme for printing */
body, .bg-theme, .content-wrapper, .card, .card-body {
background: white !important;
color: black !important;
}
/* Make all text black and readable - override inline styles */
h1, h2, h3, h4, h5, h6, p, span, td, th, a, strong, small, div {
color: black !important;
}
/* Force small text (part numbers) to be dark and readable */
small, small[style], td small {
color: #333 !important;
font-size: 11px !important;
}
/* Location link styling for print */
.location-link, .location-link i {
color: black !important;
}
/* Table styling for print */
.table {
border-collapse: collapse !important;
}
.table th, .table td {
border: 1px solid #333 !important;
padding: 8px !important;
color: black !important;
background: white !important;
}
.table thead th {
background: #eee !important;
font-weight: bold !important;
}
.table-striped tbody tr:nth-of-type(odd) {
background: #f5f5f5 !important;
}
/* Keep status colors visible but darker for print */
strong[style*="color:#ff0000"] { color: #cc0000 !important; }
strong[style*="color:#ff6600"] { color: #cc5500 !important; }
/* Hide non-essential elements */
#sidebar-wrapper, .topbar-nav, .back-to-top, .footer,
#pageloader-overlay, #vendorFilter, #refreshBtn,
.location-popup, .location-popup-overlay {
display: none !important;
}
/* Expand content to full width */
.content-wrapper {
margin-left: 0 !important;
padding: 0 !important;
}
.card {
border: none !important;
box-shadow: none !important;
}
/* Part number styling */
.card-body small {
color: #666 !important;
}
}
</style>
<!-- Location map popup modal -->
<style>
/* Theme-specific styling for location links */