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

@@ -154,6 +154,31 @@ PCs are in the machines table, identified by:
- `pctypeid IS NOT NULL`
- `machinetypeid IN (33, 34, 35)`
### IIS 401 Error on New ASP Files
**Problem:** When creating new ASP files from scratch, they may return HTTP 200 on the first request but HTTP 401 Unauthorized on all subsequent requests.
**Symptoms:**
- First curl/browser request to new .asp file returns 200
- All following requests return 401 Unauthorized
- Existing files (like printerqrbatch.asp) work fine with consistent 200 responses
- Adding `<%@ Language=VBScript %>` directive alone does NOT fix it
**Solution:** Instead of creating new ASP files from scratch, **copy an existing working ASP file** to the new filename, then modify its contents:
```bash
# DON'T do this (creates file from scratch - will get 401 errors):
# Write tool to create newfile.asp
# DO this instead (copy existing working file):
cp printerqrbatch.asp newfile.asp
# Then use Edit tool to replace the content
```
**Why this works:** The exact root cause is unclear, but appears related to how IIS handles file metadata/permissions for newly created files vs. copied files. Copying preserves whatever attributes IIS needs to serve the file correctly.
**Verified fix:** Files created by copying printerqrbatch.asp consistently return 200 on multiple sequential requests.
## API Endpoints
**Base URL:** `http://192.168.122.151:8080/api.asp`

View File

@@ -1,15 +1,12 @@
# ShopDB - Future TODO List
**Created:** 2025-11-25
**Last Updated:** 2025-11-25
**Last Updated:** 2026-01-16
---
## High Priority
### Outstanding Bugs
- [ ] Fix displaysubnet.asp - Runtime error (subscript out of range)
### Uncommitted Changes
- [ ] Review and commit pending changes:
- api.asp
@@ -68,6 +65,9 @@
## Completed (Reference)
### December 2025
- [x] Fix displaysubnet.asp runtime error - Phase 2 schema update (Dec 29)
### November 2025
- [x] Phase 1: Schema changes (Nov 6)
- [x] Phase 2: PC migration (Nov 10)

View File

@@ -311,7 +311,7 @@
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="isshopfloor" name="isshopfloor" value="1">
<input type="checkbox" class="custom-control-input" id="isshopfloor" name="isshopfloor" value="1" checked>
<label class="custom-control-label" for="isshopfloor">Show on Shopfloor Dashboard</label>
</div>
<small class="form-text text-muted">Check this to display on the shopfloor TV dashboard (72-hour window)</small>

View File

@@ -3,6 +3,8 @@
<head>
<!--#include file="./includes/header.asp"-->
<!--#include file="./includes/sql.asp"-->
<!-- JsBarcode for barcode generation -->
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
</head>
<%
@@ -60,15 +62,22 @@ End If
<form id="scanForm" method="post" action="./saveusbdirect.asp">
<div class="form-group">
<label for="serialnumber">Serial Number</label>
<input
type="text"
class="form-control form-control-lg"
id="serialnumber"
name="serialnumber"
placeholder="Scan barcode or type serial number..."
autocomplete="off"
autofocus
style="font-size: 24px; text-align: center; padding: 20px; font-family: monospace; letter-spacing: 2px;">
<div class="input-group">
<input
type="text"
class="form-control form-control-lg"
id="serialnumber"
name="serialnumber"
placeholder="Scan barcode or type serial number..."
autocomplete="off"
autofocus
style="font-size: 24px; text-align: center; padding: 20px; font-family: monospace; letter-spacing: 2px;">
<div class="input-group-append">
<button type="button" class="btn btn-info btn-lg" id="generateBtn" title="Generate random 8-digit serial">
<i class="zmdi zmdi-refresh"></i> Generate
</button>
</div>
</div>
</div>
<div class="form-group">
@@ -106,6 +115,25 @@ Set rsBU = Nothing
</button>
</div>
</form>
<!-- Barcode Preview Section -->
<div id="barcodeSection" style="display:none; margin-top:30px; border-top:1px solid #444; padding-top:20px;">
<h6><i class="zmdi zmdi-label"></i> Barcode Label Preview</h6>
<p class="text-muted small">Print this on a Uline sticker label. Click the barcode to print.</p>
<div id="barcodePreview" style="background:#fff; padding:10px; display:inline-block; border-radius:4px; cursor:pointer;" onclick="printBarcode()" title="Click to print">
<svg id="barcodeSvg"></svg>
</div>
<div style="margin-top:10px;">
<button type="button" class="btn btn-sm btn-outline-light" onclick="printBarcode()">
<i class="zmdi zmdi-print"></i> Quick Print
</button>
<a id="labelPageLink" href="#" target="_blank" class="btn btn-sm btn-outline-info" style="display:none;">
<i class="zmdi zmdi-label"></i> Print Label Page
</a>
</div>
</div>
</div>
<div id="successArea" style="display:none;">
@@ -191,6 +219,23 @@ $(document).ready(function() {
$('#serialnumber').val('').focus();
}
// Generate random 8-digit serial number
$('#generateBtn').on('click', function() {
var serial = generateSerial();
$('#serialnumber').val(serial);
updateBarcode(serial);
});
// Update barcode when serial number changes
$('#serialnumber').on('input', function() {
var serial = $(this).val().trim();
if (serial.length >= 3) {
updateBarcode(serial);
} else {
$('#barcodeSection').hide();
}
});
// Validate form before submission
$('#scanForm').on('submit', function(e) {
var serial = $('#serialnumber').val().trim();
@@ -202,6 +247,76 @@ $(document).ready(function() {
}
});
});
// Generate random 8-digit serial number
function generateSerial() {
var min = 10000000; // 8 digits minimum
var max = 99999999; // 8 digits maximum
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Update barcode preview
function updateBarcode(serial) {
if (serial && serial.length >= 3) {
try {
JsBarcode("#barcodeSvg", serial, {
format: "CODE128",
width: 1.5,
height: 30,
displayValue: true,
fontSize: 12,
margin: 5,
textMargin: 2
});
$('#barcodeSection').show();
$('#labelPageLink').attr('href', './usbsingle.asp?serial=' + encodeURIComponent(serial)).show();
} catch (e) {
console.error('Barcode error:', e);
$('#barcodeSection').hide();
$('#labelPageLink').hide();
}
} else {
$('#barcodeSection').hide();
$('#labelPageLink').hide();
}
}
// Print barcode label
function printBarcode() {
var serial = $('#serialnumber').val().trim();
if (!serial) {
alert('Please enter a serial number first');
return;
}
// Create print window with just the barcode
var printWindow = window.open('', '_blank', 'width=300,height=200');
printWindow.document.write('<html><head><title>USB Label - ' + serial + '</title>');
printWindow.document.write('<style>');
printWindow.document.write('body { margin: 0; padding: 5px; font-family: Arial, sans-serif; }');
printWindow.document.write('.label { text-align: center; }');
printWindow.document.write('@media print { @page { size: 1in 0.5in; margin: 0; } }');
printWindow.document.write('</style>');
printWindow.document.write('<scr' + 'ipt src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></scr' + 'ipt>');
printWindow.document.write('</head><body>');
printWindow.document.write('<div class="label">');
printWindow.document.write('<svg id="printBarcode"></svg>');
printWindow.document.write('</div>');
printWindow.document.write('<scr' + 'ipt>');
printWindow.document.write('JsBarcode("#printBarcode", "' + serial + '", {');
printWindow.document.write(' format: "CODE128",');
printWindow.document.write(' width: 1.2,');
printWindow.document.write(' height: 25,');
printWindow.document.write(' displayValue: true,');
printWindow.document.write(' fontSize: 10,');
printWindow.document.write(' margin: 2,');
printWindow.document.write(' textMargin: 1');
printWindow.document.write('});');
printWindow.document.write('setTimeout(function() { window.print(); window.close(); }, 300);');
printWindow.document.write('</scr' + 'ipt>');
printWindow.document.write('</body></html>');
printWindow.document.close();
}
</script>
</body>

77
api.asp
View File

@@ -853,8 +853,9 @@ Sub GetShopfloorPCs()
' Build WHERE clause with optional filters
' Dashboard (11) and Lobby Display (12) can be on any subnet, others require 10.134.*
' Only include actual PCs (machinetypeid 33-35), not USB devices (44), Part Markers (45), Firewalls (46), etc.
whereClause = "WHERE m.isactive = 1 " & _
"AND m.machinetypeid >= 33 "
"AND m.machinetypeid IN (33, 34, 35) "
' Add pctypeid filter if provided
If filterPcTypeId <> "" And IsNumeric(filterPcTypeId) Then
@@ -881,7 +882,11 @@ Sub GetShopfloorPCs()
"m.loggedinuser, m.lastupdated, m.pctypeid, m.businessunitid, " & _
"c.address AS ipaddress, " & _
"COALESCE(pt.typename, 'Uncategorized') AS pctype, " & _
"COALESCE(bu.businessunit, 'TBD') AS businessunit " & _
"COALESCE(bu.businessunit, 'TBD') AS businessunit, " & _
"(SELECT GROUP_CONCAT(eq2.machinenumber ORDER BY eq2.machinenumber SEPARATOR ', ') " & _
"FROM machinerelationships mr2 " & _
"JOIN machines eq2 ON mr2.machineid = eq2.machineid AND eq2.pctypeid IS NULL " & _
"WHERE mr2.related_machineid = m.machineid AND mr2.relationshiptypeid = 3 AND mr2.isactive = 1) AS equipment_number " & _
"FROM machines m " & _
"LEFT JOIN communications c ON m.machineid = c.machineid AND c.isprimary = 1 AND c.comstypeid = 1 " & _
"LEFT JOIN pctype pt ON m.pctypeid = pt.pctypeid " & _
@@ -907,7 +912,7 @@ Sub GetShopfloorPCs()
pcData = "{"
pcData = pcData & """machineid"":" & rsPC("machineid") & ","
pcData = pcData & """hostname"":""" & EscapeJSON(rsPC("hostname") & "") & ""","
pcData = pcData & """machinenumber"":""" & EscapeJSON(rsPC("machinenumber") & "") & ""","
pcData = pcData & """machinenumber"":""" & EscapeJSON(rsPC("equipment_number") & "") & ""","
pcData = pcData & """serialnumber"":""" & EscapeJSON(rsPC("serialnumber") & "") & ""","
pcData = pcData & """ipaddress"":""" & EscapeJSON(rsPC("ipaddress") & "") & ""","
pcData = pcData & """loggedinuser"":""" & EscapeJSON(rsPC("loggedinuser") & "") & ""","
@@ -1794,10 +1799,18 @@ Function CreatePCMachineRelationship(pcMachineid, machineNumber)
Set rsResult = objConn.Execute(strSQL)
If Not rsResult.EOF Then
' Relationship already exists
' Relationship already exists - still propagate to dualpath partners
LogToFile "CreatePCMachineRelationship: Relationship already exists (relationshipid=" & rsResult("relationshipid") & ")"
rsResult.Close
Set rsResult = Nothing
' Propagate controller to any dualpath machines that may be missing it
Dim dualpathCountExisting
dualpathCountExisting = PropagateControllerToDualpathMachinesAPI(CLng(equipmentMachineid), CLng(pcMachineid))
If dualpathCountExisting > 0 Then
LogToFile "Propagated controller to " & dualpathCountExisting & " dualpath machine(s) from existing relationship"
End If
CreatePCMachineRelationship = True
Exit Function
End If
@@ -1845,49 +1858,85 @@ End Function
Function PropagateControllerToDualpathMachinesAPI(equipmentMachineid, pcMachineid)
On Error Resume Next
LogToFile "PropagateDP: Starting for equipment=" & equipmentMachineid & " pc=" & pcMachineid
Dim rsDP, rsDPCheck, controlsTypeID, dualpathMachineId, cnt
cnt = 0
' Get Controls relationship type ID
Set rsDP = objConn.Execute("SELECT relationshiptypeid FROM relationshiptypes WHERE relationshiptype = 'Controls'")
If Err.Number <> 0 Then
LogToFile "PropagateDP: ERROR getting Controls type: " & Err.Description
Err.Clear
PropagateControllerToDualpathMachinesAPI = 0
Exit Function
End If
If rsDP.EOF Then
LogToFile "PropagateDP: Controls relationship type not found in relationshiptypes table"
PropagateControllerToDualpathMachinesAPI = 0
rsDP.Close
Set rsDP = Nothing
Exit Function
End If
controlsTypeID = CLng(rsDP("relationshiptypeid"))
LogToFile "PropagateDP: Controls type ID = " & controlsTypeID
rsDP.Close
Set rsDP = Nothing
' Find all machines with dualpath relationship to this equipment
Set rsDP = objConn.Execute("SELECT related_machineid FROM machinerelationships mr " & _
Dim dpSQL
dpSQL = "SELECT related_machineid FROM machinerelationships mr " & _
"JOIN relationshiptypes rt ON mr.relationshiptypeid = rt.relationshiptypeid " & _
"WHERE mr.machineid = " & CLng(equipmentMachineid) & " " & _
"AND rt.relationshiptype = 'Dualpath' AND mr.isactive = 1")
"AND rt.relationshiptype = 'Dualpath' AND mr.isactive = 1"
LogToFile "PropagateDP: Finding dualpath partners: " & dpSQL
Set rsDP = objConn.Execute(dpSQL)
If Err.Number <> 0 Then
LogToFile "PropagateDP: ERROR finding dualpath partners: " & Err.Description
Err.Clear
PropagateControllerToDualpathMachinesAPI = 0
Exit Function
End If
If rsDP.EOF Then
LogToFile "PropagateDP: No dualpath partners found for equipment " & equipmentMachineid
End If
Do While Not rsDP.EOF
dualpathMachineId = CLng(rsDP("related_machineid"))
LogToFile "PropagateDP: Found dualpath partner machineid=" & dualpathMachineId
' Check if this dualpath machine already has a Controls relationship with this PC
Set rsDPCheck = objConn.Execute("SELECT relationshipid FROM machinerelationships " & _
"WHERE machineid = " & CLng(pcMachineid) & " " & _
"AND related_machineid = " & dualpathMachineId & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
' Pattern: Equipment -> Controls -> PC (machineid=equipment, related_machineid=PC)
Dim checkSQL
checkSQL = "SELECT relationshipid FROM machinerelationships " & _
"WHERE machineid = " & dualpathMachineId & " " & _
"AND related_machineid = " & CLng(pcMachineid) & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1"
LogToFile "PropagateDP: Checking existing: " & checkSQL
Set rsDPCheck = objConn.Execute(checkSQL)
If rsDPCheck.EOF Then
' Create Controls relationship: PC -> Dualpath Machine
LogToFile "PropagateDP: No existing relationship, creating new one"
' Create Controls relationship: Dualpath Equipment -> PC (matches existing data pattern)
Dim cmdDPAPI
Set cmdDPAPI = Server.CreateObject("ADODB.Command")
cmdDPAPI.ActiveConnection = objConn
cmdDPAPI.CommandText = "INSERT INTO machinerelationships (machineid, related_machineid, relationshiptypeid, isactive) VALUES (?, ?, ?, 1)"
cmdDPAPI.Parameters.Append cmdDPAPI.CreateParameter("@pcid", 3, 1, , CLng(pcMachineid))
cmdDPAPI.Parameters.Append cmdDPAPI.CreateParameter("@equipid", 3, 1, , dualpathMachineId)
cmdDPAPI.Parameters.Append cmdDPAPI.CreateParameter("@pcid", 3, 1, , CLng(pcMachineid))
cmdDPAPI.Parameters.Append cmdDPAPI.CreateParameter("@reltypeid", 3, 1, , controlsTypeID)
cmdDPAPI.Execute
If Err.Number <> 0 Then
LogToFile "PropagateDP: ERROR inserting relationship: " & Err.Description
Err.Clear
Else
cnt = cnt + 1
LogToFile "PropagateDP: SUCCESS created Controls relationship: Equipment " & dualpathMachineId & " -> PC " & pcMachineid
End If
Set cmdDPAPI = Nothing
cnt = cnt + 1
LogToFile "Created dualpath Controls relationship: Equipment " & dualpathMachineId & " controlled by PC " & pcMachineid
Else
LogToFile "PropagateDP: Relationship already exists (id=" & rsDPCheck("relationshipid") & ")"
End If
rsDPCheck.Close
Set rsDPCheck = Nothing

View File

@@ -68,6 +68,20 @@
"LEFT JOIN vendors v ON m.vendorid = v.vendorid " & _
"LEFT JOIN communications c ON mac.machineid = c.machineid AND c.isprimary = 1 AND c.comstypeid = 1 " & _
"WHERE mac.machineid = " & CLng(deviceId) & " AND mac.machinetypeid = 19"
Case "firewall"
machineTypeId = 46
editUrl = "devicefirewall.asp?id=" & deviceId
listUrl = "networkdevices.asp?filter=Firewall"
strSQL = "SELECT mac.machineid, mac.alias AS firewallname, mac.machinenotes AS description, " & _
"mac.maptop, mac.mapleft, mac.isactive, mac.serialnumber, mac.fqdn, mac.logicmonitorurl, " & _
"v.vendor, m.modelnumber, m.image, c.address AS ipaddress, c.macaddress, " & _
"NULL AS idfname, 'Firewall' AS devicetype, " & _
"mac.alias AS devicename " & _
"FROM machines mac " & _
"LEFT JOIN models m ON mac.modelnumberid = m.modelnumberid " & _
"LEFT JOIN vendors v ON m.vendorid = v.vendorid " & _
"LEFT JOIN communications c ON mac.machineid = c.machineid AND c.isprimary = 1 AND c.comstypeid = 1 " & _
"WHERE mac.machineid = " & CLng(deviceId) & " AND mac.machinetypeid = 46"
Case "camera"
machineTypeId = 18
editUrl = "devicecamera.asp?id=" & deviceId
@@ -121,6 +135,8 @@
deviceName = rs("servername")
Case "switch"
deviceName = rs("switchname")
Case "firewall"
deviceName = rs("firewallname")
Case "camera"
deviceName = rs("cameraname")
Case "accesspoint", "access point"
@@ -206,7 +222,7 @@
<%
Dim showLogicMonitor
showLogicMonitor = False
If LCase(deviceType) = "server" Or LCase(deviceType) = "switch" Or LCase(deviceType) = "accesspoint" Or LCase(deviceType) = "access point" Then
If LCase(deviceType) = "server" Or LCase(deviceType) = "switch" Or LCase(deviceType) = "firewall" Or LCase(deviceType) = "accesspoint" Or LCase(deviceType) = "access point" Then
If Not IsNull(rs("logicmonitorurl")) Then
If rs("logicmonitorurl") & "" <> "" Then showLogicMonitor = True
End If

View File

@@ -62,7 +62,7 @@
<div class="clearfix"></div>
<div class="content-wrapper">
<div class="row">
<div class="col-xl-auto">
<div class="col-12">
<div class="card">
<div class="card-body">
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:15px;">

View File

@@ -407,8 +407,8 @@ End If
<div class="tab-pane" id="relationships">
<h5 class="mb-3">Machine Relationships</h5>
<!-- Controlling PCs -->
<h6 class="mt-3 mb-2"><i class="zmdi zmdi-desktop-mac"></i> Controlled By PC</h6>
<!-- Connected PCs -->
<h6 class="mt-3 mb-2"><i class="zmdi zmdi-desktop-mac"></i> Connected PCs</h6>
<div class="table-responsive mb-4">
<table class="table table-hover table-striped">
<thead>
@@ -421,36 +421,54 @@ End If
</thead>
<tbody>
<%
' Query PCs that control this machine (directly or via dualpath)
' Query ALL PCs related to this machine via machinerelationships
' Check both directions - the PC is identified by pctypeid IS NOT NULL
' Use GROUP_CONCAT to combine multiple IPs into one row per PC
strSQL2 = "SELECT m.machineid, m.machinenumber, m.hostname, " & _
"GROUP_CONCAT(DISTINCT c.address ORDER BY c.address SEPARATOR ', ') as address, 'Controls' as relationshiptype " & _
strSQL2 = "SELECT m.machineid, m.machinenumber, m.hostname, rt.relationshiptype, " & _
"GROUP_CONCAT(DISTINCT c.address ORDER BY c.address SEPARATOR ', ') as address " & _
"FROM machinerelationships mr " & _
"JOIN relationshiptypes rt ON mr.relationshiptypeid = rt.relationshiptypeid " & _
"JOIN machines m ON (mr.machineid = m.machineid OR mr.related_machineid = m.machineid) " & _
"LEFT JOIN communications c ON m.machineid = c.machineid AND c.comstypeid IN (1, 3) AND c.isactive = 1 " & _
"WHERE (mr.machineid = ? OR mr.related_machineid = ?) AND mr.relationshiptypeid = 3 " & _
"WHERE (mr.machineid = ? OR mr.related_machineid = ?) " & _
" AND m.pctypeid IS NOT NULL AND m.machineid <> ? AND mr.isactive = 1 " & _
"GROUP BY m.machineid, m.machinenumber, m.hostname"
"GROUP BY m.machineid, m.machinenumber, m.hostname, rt.relationshiptype " & _
"ORDER BY rt.relationshiptype, m.hostname"
Set rs2 = ExecuteParameterizedQuery(objConn, strSQL2, Array(machineid, machineid, machineid))
Dim pcHostname, pcIP, pcMachineID
Dim pcHostname, pcIP, pcMachineID, pcLocation, pcRelType
If rs2.EOF Then
Response.Write("<tr><td colspan='4' class='text-muted text-center'>No controlling PC assigned</td></tr>")
Response.Write("<tr><td colspan='4' class='text-muted text-center'>No connected PCs</td></tr>")
Else
Do While Not rs2.EOF
pcHostname = rs2("hostname") & ""
pcIP = rs2("address") & ""
pcMachineID = rs2("machineid")
pcLocation = rs2("machinenumber") & ""
pcRelType = rs2("relationshiptype") & ""
If pcHostname = "" Then pcHostname = rs2("machinenumber") & ""
If pcHostname = "" Then pcHostname = pcLocation
If pcIP = "" Then pcIP = "<span class='text-muted'>N/A</span>"
If pcLocation = "" Then pcLocation = "N/A"
' Badge color based on relationship type
Dim pcRelBadge
Select Case LCase(pcRelType)
Case "controls"
pcRelBadge = "badge-primary"
Case "dualpath"
pcRelBadge = "badge-warning"
Case "connected to"
pcRelBadge = "badge-success"
Case Else
pcRelBadge = "badge-info"
End Select
Response.Write("<tr>")
Response.Write("<td><a href='./displaypc.asp?machineid=" & pcMachineID & "'>" & Server.HTMLEncode(pcHostname) & "</a></td>")
Response.Write("<td>" & pcIP & "</td>")
Response.Write("<td class='text-center'><a href='#' class='location-link text-info' data-machineid='" & pcMachineID & "' data-name='" & Server.HTMLEncode(pcHostname) & "'><i class='zmdi zmdi-pin' style='font-size:1.2rem;'></i></a></td>")
Response.Write("<td><span class='badge badge-primary'>" & Server.HTMLEncode(rs2("relationshiptype") & "") & "</span></td>")
Response.Write("<td>" & Server.HTMLEncode(pcLocation) & " <a href='#' class='location-link text-info' data-machineid='" & pcMachineID & "' data-name='" & Server.HTMLEncode(pcHostname) & "'><i class='zmdi zmdi-pin'></i></a></td>")
Response.Write("<td><span class='badge " & pcRelBadge & "'>" & Server.HTMLEncode(pcRelType) & "</span></td>")
Response.Write("</tr>")
rs2.MoveNext
Loop

View File

@@ -87,14 +87,14 @@ Dim theme, strSQL, rs, objConn
<%
' Build WHERE clause with optional BU filter
' NOTE: Exclude LocationOnly (1), network devices (16-20), and PC types (33+)
' NOTE: Exclude LocationOnly (1), network devices (16-20, 46), and PC types (33)
Dim whereClause
whereClause = "models.machinetypeid = machinetypes.machinetypeid AND " &_
"machines.modelnumberid = models.modelnumberid AND " &_
"models.vendorid = vendors.vendorid AND " &_
"machines.businessunitid = businessunits.businessunitID AND " &_
"machines.isactive = 1 AND islocationonly=0 AND " &_
"models.machinetypeid NOT IN (1, 16, 17, 18, 19, 20) AND models.machinetypeid < 33"
"models.machinetypeid NOT IN (1, 16, 17, 18, 19, 20, 33, 46)"
' Add BU filter if specified
If filterBU <> "" And IsNumeric(filterBU) Then

View File

@@ -524,8 +524,8 @@ End If
<%
End If
%>
<!-- Machines Controlled by This PC -->
<h6 class="mt-3 mb-2"><i class="zmdi zmdi-settings"></i> Machines Controlled by This PC</h6>
<!-- Connected Equipment -->
<h6 class="mt-3 mb-2"><i class="zmdi zmdi-settings"></i> Connected Equipment</h6>
<div class="table-responsive mb-4">
<table class="table table-hover table-striped">
<thead>
@@ -533,42 +533,59 @@ End If
<th>Machine Number</th>
<th>Type</th>
<th>Model</th>
<th>Location</th>
<th>Relationship</th>
</tr>
</thead>
<tbody>
<%
' Query machines that THIS PC controls
' Query ALL equipment related to this PC via machinerelationships
' Check both directions - the equipment is identified by pctypeid IS NULL
strSQL2 = "SELECT m.machineid, m.machinenumber, mt.machinetype, mo.modelnumber, 'Controls' as relationshiptype " & _
strSQL2 = "SELECT m.machineid, m.machinenumber, mt.machinetype, mo.modelnumber, rt.relationshiptype " & _
"FROM machinerelationships mr " & _
"JOIN relationshiptypes rt ON mr.relationshiptypeid = rt.relationshiptypeid " & _
"JOIN machines m ON (mr.machineid = m.machineid OR mr.related_machineid = m.machineid) " & _
"LEFT JOIN models mo ON m.modelnumberid = mo.modelnumberid " & _
"LEFT JOIN machinetypes mt ON mo.machinetypeid = mt.machinetypeid " & _
"WHERE (mr.machineid = ? OR mr.related_machineid = ?) AND mr.relationshiptypeid = 3 " & _
"WHERE (mr.machineid = ? OR mr.related_machineid = ?) " & _
" AND m.pctypeid IS NULL AND m.machineid <> ? AND mr.isactive = 1 " & _
"ORDER BY machinenumber"
"ORDER BY rt.relationshiptype, m.machinenumber"
Set rs2 = ExecuteParameterizedQuery(objConn, strSQL2, Array(machineid, machineid, machineid))
If rs2.EOF Then
Response.Write("<tr><td colspan='4' class='text-muted text-center'>This PC does not control any machines</td></tr>")
Response.Write("<tr><td colspan='5' class='text-muted text-center'>No connected equipment</td></tr>")
Else
Do While Not rs2.EOF
Dim ctrlMachineNum, ctrlType, ctrlModel, ctrlMachineID
Dim ctrlMachineNum, ctrlType, ctrlModel, ctrlMachineID, ctrlRelType
ctrlMachineNum = rs2("machinenumber") & ""
ctrlType = rs2("machinetype") & ""
ctrlModel = rs2("modelnumber") & ""
ctrlMachineID = rs2("machineid")
ctrlRelType = rs2("relationshiptype") & ""
If ctrlMachineNum = "" Then ctrlMachineNum = "<span class='text-muted'>N/A</span>"
If ctrlMachineNum = "" Then ctrlMachineNum = "N/A"
If ctrlType = "" Then ctrlType = "<span class='text-muted'>N/A</span>"
If ctrlModel = "" Then ctrlModel = "<span class='text-muted'>N/A</span>"
' Badge color based on relationship type
Dim ctrlRelBadge
Select Case LCase(ctrlRelType)
Case "controls"
ctrlRelBadge = "badge-primary"
Case "dualpath"
ctrlRelBadge = "badge-warning"
Case "connected to"
ctrlRelBadge = "badge-success"
Case Else
ctrlRelBadge = "badge-info"
End Select
Response.Write("<tr>")
Response.Write("<td><a href='./displaymachine.asp?machineid=" & ctrlMachineID & "'>" & Server.HTMLEncode(ctrlMachineNum) & "</a></td>")
Response.Write("<td>" & ctrlType & "</td>")
Response.Write("<td>" & ctrlModel & "</td>")
Response.Write("<td><span class='badge badge-success'>" & Server.HTMLEncode(rs2("relationshiptype") & "") & "</span></td>")
Response.Write("<td>" & Server.HTMLEncode(ctrlMachineNum) & " <a href='#' class='location-link text-info' data-machineid='" & ctrlMachineID & "' data-name='" & Server.HTMLEncode(ctrlMachineNum) & "'><i class='zmdi zmdi-pin'></i></a></td>")
Response.Write("<td><span class='badge " & ctrlRelBadge & "'>" & Server.HTMLEncode(ctrlRelType) & "</span></td>")
Response.Write("</tr>")
rs2.MoveNext
Loop

View File

@@ -182,7 +182,15 @@
End If
%>
</p>
<p class="mb-2"><%=Server.HTMLEncode(fqdnVal)%></p>
<p class="mb-2">
<%
If fqdnVal <> "N/A" And fqdnVal <> "" Then
Response.Write("<a href='http://" & Server.HTMLEncode(fqdnVal) & "' title='Click to Access Printer Admin Page' target='_blank'>" & Server.HTMLEncode(fqdnVal) & "</a>")
Else
Response.Write("N/A")
End If
%>
</p>
<p class="mb-2"><%=Server.HTMLEncode(pinVal)%></p>
<p class="mb-2">
<%

View File

@@ -43,9 +43,9 @@
<table class="table table-hover">
<thead>
<tr>
<th scope="col"></th>
<th scope="col"><i class="zmdi zmdi-pin"></i></th>
<th scope="col"><i class="zmdi zmdi-attachment-alt"></i></th>
<th scope="col">ID</th>
<th scope="col"><i class="zmdi zmdi-download"></i></th>
<th scope="col">Machine</th>
<th scope="col">Vendor</th>
<th scope="col">Model</th>
@@ -92,23 +92,23 @@
Response.write("<tr>")
' Location column - just map icon
' Picture column (far left)
Response.Write("<td><a href='./displayprinter.asp?printerid=" & printer & "' title='Click to Display Printer Details'><img src='./images/printers/" & image & "' width='30px' height='30px'></a></td>")
' Location column - map icon
Response.write("<td>")
Response.write("<span class='location-link' data-machineid='" & machineid & "' style='cursor:pointer;'>")
Response.write("<i class='zmdi zmdi-pin'></i>")
Response.write("</span>")
Response.write("</td>")
' Drivers column
' Download column
If installpath <> "" Then
Response.write("<td><a href='./" & installpath & "'><i class='zmdi zmdi-download' title='Click to Download Specific Installer' style='color:#fff;'></i></a></td>")
Else
Response.write("<td><a href='./installprinter.asp?printerid=" & printer & "'><i class='zmdi zmdi-download' title='Click to Download Universal Driver Installer' style='color:#fff;'></i></a></td>")
End If
' ID column
Response.Write("<td><a href='./displayprinter.asp?printerid=" & printer & "' title='Click to Display Printer Details'><img src='./images/printers/" & image & "' width='30px' height='30px'></a></td>")
' Machine column - link to machine (or printer if location only)
' Check if location only (1 = location only)
isLocOnly = False

View File

@@ -75,6 +75,9 @@
<a href="./addusb.asp" class="btn btn-success btn-sm">
<i class="zmdi zmdi-plus-circle"></i> Add USB
</a>
<a href="./usblabelbatch.asp" class="btn btn-info btn-sm" target="_blank">
<i class="zmdi zmdi-label"></i> Batch Print Labels
</a>
</div>
</div>
@@ -176,6 +179,9 @@
<a href="./usbhistory.asp?machineid=<%=machineId%>" class="btn btn-sm btn-secondary" title="View history">
<i class="zmdi zmdi-time"></i>
</a>
<a href="./usbsingle.asp?machineid=<%=machineId%>" class="btn btn-sm btn-info" title="Print barcode label" target="_blank">
<i class="zmdi zmdi-label"></i>
</a>
</td>
</tr>
<%

View File

@@ -302,19 +302,20 @@ Function PropagateControllerToDualpathMachines(conn, equipmentMachineid, pcMachi
dualpathMachineId = CLng(rsDP("related_machineid"))
' Check if this dualpath machine already has a Controls relationship with this PC
' Pattern: Equipment -> Controls -> PC (machineid=equipment, related_machineid=PC)
Set rsDPCheck = conn.Execute("SELECT relationshipid FROM machinerelationships " & _
"WHERE machineid = " & CLng(pcMachineid) & " " & _
"AND related_machineid = " & dualpathMachineId & " " & _
"WHERE machineid = " & dualpathMachineId & " " & _
"AND related_machineid = " & CLng(pcMachineid) & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
If rsDPCheck.EOF Then
' Create Controls relationship: PC -> Dualpath Machine
' Create Controls relationship: Dualpath Equipment -> PC (matches existing data pattern)
Dim cmdDP
Set cmdDP = Server.CreateObject("ADODB.Command")
cmdDP.ActiveConnection = conn
cmdDP.CommandText = "INSERT INTO machinerelationships (machineid, related_machineid, relationshiptypeid, isactive) VALUES (?, ?, ?, 1)"
cmdDP.Parameters.Append cmdDP.CreateParameter("@pcid", 3, 1, , CLng(pcMachineid))
cmdDP.Parameters.Append cmdDP.CreateParameter("@equipid", 3, 1, , dualpathMachineId)
cmdDP.Parameters.Append cmdDP.CreateParameter("@pcid", 3, 1, , CLng(pcMachineid))
cmdDP.Parameters.Append cmdDP.CreateParameter("@reltypeid", 3, 1, , controlsTypeID)
cmdDP.Execute
Set cmdDP = Nothing
@@ -360,28 +361,29 @@ Function PropagateControllerFromDualpathMachine(conn, machineId1, machineId2)
Set rsCtrl = Nothing
' Check if machine1 has a controller, copy to machine2 if machine2 doesn't have one
Set rsCtrl = conn.Execute("SELECT machineid FROM machinerelationships " & _
"WHERE related_machineid = " & CLng(machineId1) & " " & _
' Pattern: Equipment -> Controls -> PC (machineid=equipment, related_machineid=PC)
Set rsCtrl = conn.Execute("SELECT related_machineid FROM machinerelationships " & _
"WHERE machineid = " & CLng(machineId1) & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
If Not rsCtrl.EOF Then
pcMachineid = CLng(rsCtrl("machineid"))
pcMachineid = CLng(rsCtrl("related_machineid"))
rsCtrl.Close
Set rsCtrl = Nothing
' Check if machine2 already has this controller
Set rsCheck = conn.Execute("SELECT relationshipid FROM machinerelationships " & _
"WHERE machineid = " & pcMachineid & " AND related_machineid = " & CLng(machineId2) & " " & _
"WHERE machineid = " & CLng(machineId2) & " AND related_machineid = " & pcMachineid & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
If rsCheck.EOF Then
' Create: PC -> Controls -> machine2
' Create: Equipment -> Controls -> PC
Dim cmdCtrl
Set cmdCtrl = Server.CreateObject("ADODB.Command")
cmdCtrl.ActiveConnection = conn
cmdCtrl.CommandText = "INSERT INTO machinerelationships (machineid, related_machineid, relationshiptypeid, isactive) VALUES (?, ?, ?, 1)"
cmdCtrl.Parameters.Append cmdCtrl.CreateParameter("@pcid", 3, 1, , pcMachineid)
cmdCtrl.Parameters.Append cmdCtrl.CreateParameter("@equipid", 3, 1, , CLng(machineId2))
cmdCtrl.Parameters.Append cmdCtrl.CreateParameter("@pcid", 3, 1, , pcMachineid)
cmdCtrl.Parameters.Append cmdCtrl.CreateParameter("@reltypeid", 3, 1, , controlsTypeID)
cmdCtrl.Execute
Set cmdCtrl = Nothing
@@ -395,28 +397,28 @@ Function PropagateControllerFromDualpathMachine(conn, machineId1, machineId2)
End If
' Now check if machine2 has a controller, copy to machine1 if machine1 doesn't have one
Set rsCtrl = conn.Execute("SELECT machineid FROM machinerelationships " & _
"WHERE related_machineid = " & CLng(machineId2) & " " & _
Set rsCtrl = conn.Execute("SELECT related_machineid FROM machinerelationships " & _
"WHERE machineid = " & CLng(machineId2) & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
If Not rsCtrl.EOF Then
pcMachineid = CLng(rsCtrl("machineid"))
pcMachineid = CLng(rsCtrl("related_machineid"))
rsCtrl.Close
Set rsCtrl = Nothing
' Check if machine1 already has this controller
Set rsCheck = conn.Execute("SELECT relationshipid FROM machinerelationships " & _
"WHERE machineid = " & pcMachineid & " AND related_machineid = " & CLng(machineId1) & " " & _
"WHERE machineid = " & CLng(machineId1) & " AND related_machineid = " & pcMachineid & " " & _
"AND relationshiptypeid = " & controlsTypeID & " AND isactive = 1")
If rsCheck.EOF Then
' Create: PC -> Controls -> machine1
' Create: Equipment -> Controls -> PC
Dim cmdCtrl2
Set cmdCtrl2 = Server.CreateObject("ADODB.Command")
cmdCtrl2.ActiveConnection = conn
cmdCtrl2.CommandText = "INSERT INTO machinerelationships (machineid, related_machineid, relationshiptypeid, isactive) VALUES (?, ?, ?, 1)"
cmdCtrl2.Parameters.Append cmdCtrl2.CreateParameter("@pcid", 3, 1, , pcMachineid)
cmdCtrl2.Parameters.Append cmdCtrl2.CreateParameter("@equipid", 3, 1, , CLng(machineId1))
cmdCtrl2.Parameters.Append cmdCtrl2.CreateParameter("@pcid", 3, 1, , pcMachineid)
cmdCtrl2.Parameters.Append cmdCtrl2.CreateParameter("@reltypeid", 3, 1, , controlsTypeID)
cmdCtrl2.Execute
Set cmdCtrl2 = Nothing

View File

@@ -0,0 +1,181 @@
<%
' Returns both standard and metered part numbers as pipe-delimited string
' Format: "standard|metered" or just "standard" if no metered option
Function GetSupplyPartNumbers(printerModel, supplyName)
On Error Resume Next
Dim pn, sn, isDrum, isWaste, stdPN, metPN
pn = UCase(printerModel)
sn = UCase(supplyName)
isDrum = (InStr(sn, "DRUM") > 0 Or InStr(sn, "IMAGING") > 0)
isWaste = (InStr(sn, "WASTE") > 0)
stdPN = ""
metPN = ""
' VersaLink C415
If InStr(pn, "C415") > 0 Then
If isWaste Then
stdPN = "008R13335"
ElseIf isDrum Then
stdPN = "013R00701"
ElseIf InStr(sn, "BLACK") > 0 Then
stdPN = "006R04677"
metPN = "006R04681"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "006R04678"
metPN = "006R04682"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "006R04679"
metPN = "006R04683"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "006R04680"
metPN = "006R04684"
End If
' VersaLink C405
ElseIf InStr(pn, "C405") > 0 Then
If isWaste Then
stdPN = "108R01124"
ElseIf isDrum Then
stdPN = "101R00555"
ElseIf InStr(sn, "BLACK") > 0 Then
stdPN = "106R03500"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "106R03501"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "106R03502"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "106R03503"
End If
' VersaLink C7125/C7100
ElseIf InStr(pn, "C7125") > 0 Or InStr(pn, "C7100") > 0 Then
If isWaste Then
stdPN = "115R00129"
ElseIf isDrum Then
stdPN = "013R00688"
ElseIf InStr(sn, "BLACK") > 0 Then
stdPN = "006R01824"
metPN = "006R01820"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "006R01825"
metPN = "006R01821"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "006R01826"
metPN = "006R01822"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "006R01827"
metPN = "006R01823"
End If
' VersaLink B7125
ElseIf InStr(pn, "B7125") > 0 Then
If isWaste Then
stdPN = "115R00129"
ElseIf isDrum Then
stdPN = "013R00687"
ElseIf InStr(sn, "BLACK") > 0 Or InStr(sn, "TONER") > 0 Then
stdPN = "006R01818"
metPN = "006R01819"
End If
' VersaLink B405
ElseIf InStr(pn, "B405") > 0 Then
If isWaste Then
stdPN = "108R01124"
ElseIf isDrum Then
stdPN = "101R00554"
ElseIf InStr(sn, "BLACK") > 0 Or InStr(sn, "TONER") > 0 Then
stdPN = "106R03580"
End If
' AltaLink C8135
ElseIf InStr(pn, "C8135") > 0 Then
If isWaste Then
stdPN = "008R08101"
ElseIf isDrum Then
stdPN = "013R00681"
ElseIf InStr(sn, "BLACK") > 0 Then
stdPN = "006R01746"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "006R01747"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "006R01748"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "006R01749"
End If
' Xerox EC8036/AltaLink C8036 (compatible with WC7800 and AltaLink C80xx toner)
ElseIf InStr(pn, "EC8036") > 0 Or InStr(pn, "C8036") > 0 Then
Dim altPN
altPN = ""
If isWaste Then
stdPN = "008R13061"
ElseIf isDrum Then
stdPN = "013R00677"
ElseIf InStr(sn, "BLACK") > 0 Then
stdPN = "006R01509"
altPN = "006R01697"
metPN = "006R01701"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "006R01512"
altPN = "006R01698"
metPN = "006R01702"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "006R01511"
altPN = "006R01699"
metPN = "006R01703"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "006R01510"
altPN = "006R01700"
metPN = "006R01704"
End If
' Return all 3 options for EC8036
If altPN <> "" Then
GetSupplyPartNumbers = stdPN & "|" & altPN & "|" & metPN
Exit Function
End If
' HP LaserJet Pro M454dw / M454dn / MFP M479fdw / M479fdn (414A/414X series)
ElseIf InStr(pn, "M454") > 0 Or InStr(pn, "M479") > 0 Then
If InStr(sn, "BLACK") > 0 Then
stdPN = "W2020A"
metPN = "W2020X"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "W2021A"
metPN = "W2021X"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "W2023A"
metPN = "W2023X"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "W2022A"
metPN = "W2022X"
End If
' HP LaserJet Pro M251nw / M252dw / MFP M277dw (201A/201X series)
ElseIf InStr(pn, "M251") > 0 Or InStr(pn, "M252") > 0 Or InStr(pn, "M277") > 0 Then
If InStr(sn, "BLACK") > 0 Then
stdPN = "CF400A"
metPN = "CF400X"
ElseIf InStr(sn, "CYAN") > 0 Then
stdPN = "CF401A"
metPN = "CF401X"
ElseIf InStr(sn, "MAGENTA") > 0 Then
stdPN = "CF403A"
metPN = "CF403X"
ElseIf InStr(sn, "YELLOW") > 0 Then
stdPN = "CF402A"
metPN = "CF402X"
End If
End If
If metPN <> "" Then
GetSupplyPartNumbers = stdPN & "|" & metPN
Else
GetSupplyPartNumbers = stdPN
End If
End Function
Function IsWasteSupply(supplyName)
IsWasteSupply = (InStr(1, UCase(supplyName), "WASTE") > 0)
End Function
%>

View File

@@ -1,10 +1,35 @@
<!--#include file="config.asp"-->
<%
' Employee database connection - uses centralized config
Dim objConn
Dim objConn, empConnError
empConnError = ""
Session.Timeout = APP_SESSION_TIMEOUT
On Error Resume Next
Set objConn = Server.CreateObject("ADODB.Connection")
objConn.ConnectionString = GetEmployeeConnectionString()
objConn.Open
If Err.Number <> 0 Then
empConnError = "Employee DB Connection Error: " & Err.Number & " - " & Err.Description & " (Source: " & Err.Source & ")"
Err.Clear
End If
On Error Goto 0
Set rs = Server.CreateObject("ADODB.Recordset")
' If connection failed, display error and stop
If empConnError <> "" Then
Dim connType
If USE_EMP_DSN Then
connType = "DSN-based (wjf_employees)"
Else
connType = "Direct ODBC"
End If
Response.Write "<!DOCTYPE html><html><head><title>Database Error</title></head><body>"
Response.Write "<h2>Database Connection Error</h2>"
Response.Write "<p style='color:red;'>" & Server.HTMLEncode(empConnError) & "</p>"
Response.Write "<p>Connection String Type: " & connType & "</p>"
Response.Write "</body></html>"
Response.End
End If
%>

View File

@@ -1,54 +1,37 @@
<%
' Cached Zabbix API wrapper for ALL supply levels (toner, ink, drums, maintenance kits, etc.)
' Simplified caching - no background refresh, minimal locking
%>
<!--#include file="./zabbix_all_supplies.asp"-->
<%
' Cached function for all supply levels - returns data immediately, refreshes in background if stale
' Cached function for all supply levels - simple 5-minute cache
Function GetAllPrinterSuppliesCached(hostIP)
Dim cacheKey, cacheAge, forceRefresh
On Error Resume Next
Dim cacheKey, cacheTime, cacheAge, cachedData, forceRefresh
cacheKey = "zabbix_all_supplies_" & hostIP
' Check if manual refresh was requested
forceRefresh = (Request.QueryString("refresh") = "1" And Request.QueryString("ip") = hostIP)
If forceRefresh Then
' Clear cache for manual refresh
Application.Lock
Application(cacheKey) = Empty
Application(cacheKey & "_time") = Empty
Application(cacheKey & "_refreshing") = "false"
Application.Unlock
End If
' Check if valid cache exists (without locking)
If Not forceRefresh Then
cachedData = Application(cacheKey)
cacheTime = Application(cacheKey & "_time")
' Check if cache exists
If Not IsEmpty(Application(cacheKey)) And Not forceRefresh Then
cacheAge = DateDiff("n", Application(cacheKey & "_time"), Now())
' If cache is stale (>5 min) AND not already refreshing, trigger background update
If cacheAge >= 5 And Application(cacheKey & "_refreshing") <> "true" Then
' Mark as refreshing
Application.Lock
Application(cacheKey & "_refreshing") = "true"
Application.Unlock
' Trigger async background refresh (non-blocking)
On Error Resume Next
Dim http
Set http = Server.CreateObject("MSXML2.ServerXMLHTTP.6.0")
http.Open "GET", "http://localhost/refresh_all_supplies_cache.asp?ip=" & Server.URLEncode(hostIP), True
http.Send
Set http = Nothing
On Error Goto 0
If Not IsEmpty(cachedData) And Not IsEmpty(cacheTime) Then
cacheAge = DateDiff("n", cacheTime, Now())
If cacheAge < 5 Then
' Cache is fresh, return it
GetAllPrinterSuppliesCached = cachedData
Exit Function
End If
End If
' Return cached data immediately
GetAllPrinterSuppliesCached = Application(cacheKey)
Exit Function
End If
' No cache exists - fetch initial data
Dim freshData, zabbixConnected, pingStatus, suppliesJSON
' Cache miss or stale - fetch fresh data
Dim zabbixConnected, pingStatus, suppliesJSON
zabbixConnected = ZabbixLogin()
@@ -66,13 +49,13 @@ Function GetAllPrinterSuppliesCached(hostIP)
resultData(1) = pingStatus
resultData(2) = suppliesJSON
' Cache the result
' Cache the result (brief lock)
Application.Lock
Application(cacheKey) = resultData
Application(cacheKey & "_time") = Now()
Application(cacheKey & "_refreshing") = "false"
Application.Unlock
On Error Goto 0
GetAllPrinterSuppliesCached = resultData
End Function

View File

@@ -282,3 +282,4 @@ function escapeHtml(text) {
</script>
</body>
</html>

View File

@@ -1,3 +1,4 @@
<%@ Language=VBScript %>
<%
'=============================================================================
' FILE: savecheckinusb.asp

View File

@@ -1,3 +1,4 @@
<%@ Language=VBScript %>
<%
'=============================================================================
' FILE: savecheckoutusb.asp

View File

@@ -1,3 +1,4 @@
<%@ Language=VBScript %>
<%
'=============================================================================
' FILE: saveusbdirect.asp

View File

@@ -0,0 +1,22 @@
-- ============================================================
-- Add Dashboard and Lobby Display PC Types and Applications
-- Run against production database
-- Date: 2026-01-14
-- ============================================================
-- Add new PC types
INSERT INTO pctype (typename, description, functionalaccountid, isactive, displayorder) VALUES
('Dashboard', 'Shopfloor dashboard display PC', 3, '1', 10),
('Lobby Display', 'Lobby/TV dashboard display PC', 3, '1', 11);
-- Add new applications
INSERT INTO applications (appname, appdescription, supportteamid, isinstallable, isactive, ishidden) VALUES
('GE Aerospace Dashboard', 'Shopfloor dashboard kiosk display application', 1, b'1', b'1', b'0'),
('GE Aerospace Lobby Display', 'Lobby/TV dashboard kiosk display application', 1, b'1', b'1', b'0');
-- Verify insertions
SELECT 'PC Types Added:' AS '';
SELECT pctypeid, typename, description FROM pctype WHERE typename IN ('Dashboard', 'Lobby Display');
SELECT 'Applications Added:' AS '';
SELECT appid, appname, appdescription FROM applications WHERE appname LIKE 'GE Aerospace Dashboard' OR appname LIKE 'GE Aerospace Lobby Display';

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 */

595
uptimemap.asp Normal file
View File

@@ -0,0 +1,595 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!--#include file="./includes/header.asp"-->
<!--#include file="./includes/sql.asp"-->
<link rel="stylesheet" href="./leaflet/leaflet.css">
<script src="./leaflet/leaflet.js"></script>
</head>
<%
theme = Request.Cookies("theme")
IF theme = "" THEN
theme="bg-theme1"
END IF
' Get minimum uptime filter from query string (default 10 days)
Dim minUptime
minUptime = Request.QueryString("minuptime")
If minUptime = "" Or Not IsNumeric(minUptime) Then
minUptime = 10
Else
minUptime = CInt(minUptime)
End If
%>
<body class="bg-theme <%Response.Write(theme)%>">
<!-- start loader -->
<div id="pageloader-overlay" class="visible incoming"><div class="loader-wrapper-outer"><div class="loader-wrapper-inner" ><div class="loader"></div></div></div></div>
<!-- end loader -->
<!-- Start wrapper-->
<div id="wrapper">
<!--#include file="./includes/leftsidebar.asp"-->
<!--Start topbar header-->
<!--#include file="./includes/topbarheader.asp"-->
<!--End topbar header-->
<div class="clearfix"></div>
<div class="content-wrapper">
<div class="row">
<div class="col-lg-9">
<div class="card">
<div class="card-body" style="padding:0;">
<div style="padding:15px; border-bottom:1px solid #444;">
<h5 class="card-title" style="margin:0; display:inline-block;">
<i class='zmdi zmdi-time-interval'></i>&nbsp;&nbsp;High Uptime PCs with Machine Relationships
</h5>
<div style="float:right;">
<input type="text" id="pcSearch" class="form-control form-control-sm" placeholder="Search hostname, machine..." style="display:inline-block; width:180px; margin-right:10px;">
<label style="margin-right:5px; display:inline-block; color:#aaa;">Min Uptime:</label>
<select id="uptimeFilter" class="form-control form-control-sm" style="display:inline-block; width:100px; margin-right:10px;">
<option value="10" <%If minUptime = 10 Then Response.Write("selected")%>>10+ days</option>
<option value="20" <%If minUptime = 20 Then Response.Write("selected")%>>20+ days</option>
<option value="30" <%If minUptime = 30 Then Response.Write("selected")%>>30+ days</option>
<option value="60" <%If minUptime = 60 Then Response.Write("selected")%>>60+ days</option>
<option value="90" <%If minUptime = 90 Then Response.Write("selected")%>>90+ days</option>
</select>
<label style="margin-right:5px; display:inline-block; color:#aaa;">BU:</label>
<select id="businessUnitFilter" class="form-control form-control-sm" style="display:inline-block; width:120px;">
<option value="all">All</option>
<%
' Get business units for dropdown
Dim rsBU, strBUSQL
strBUSQL = "SELECT businessunitid, businessunit FROM businessunits WHERE isactive = 1 ORDER BY businessunit"
Set rsBU = objConn.Execute(strBUSQL)
Do While Not rsBU.EOF
Response.Write("<option value='" & rsBU("businessunitid") & "'>" & Server.HTMLEncode(rsBU("businessunit")) & "</option>")
rsBU.MoveNext
Loop
rsBU.Close
Set rsBU = Nothing
%>
</select>
</div>
</div>
<div id="map"></div>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="card">
<div class="card-header" style="background: linear-gradient(45deg, #ff6b6b, #ee5a24); color: white;">
<i class="zmdi zmdi-time-interval"></i> Uptime Legend
</div>
<div class="card-body">
<p style="font-size:12px; color:#aaa; margin-bottom:15px;">
PCs with uptime > <%=minUptime%> days that need to be rebooted.
</p>
<div style="margin-bottom:20px;">
<div style="margin:8px 0; display:flex; align-items:center;">
<span style="display:inline-block; width:14px; height:14px; background:#F44336; border-radius:50%; margin-right:10px; border:2px solid #fff; box-shadow:0 1px 3px rgba(0,0,0,0.5);"></span>
<span style="font-size:13px; color:#fff;">Needs Reboot (<%=minUptime%>+ days)</span>
</div>
</div>
<div id="statsPanel" style="margin-top:20px; padding:15px; background:#2a2a2a; border-radius:4px;">
<strong style="color:#4fc3f7;">Statistics:</strong>
<div id="pcCount" style="margin-top:10px; font-size:14px; color:#fff;">Loading...</div>
</div>
<div style="margin-top:20px; padding:15px; background:#2a2a2a; border-radius:4px; font-size:12px;">
<strong style="color:#4fc3f7;">Tips:</strong>
<ul style="margin:8px 0; padding-left:20px; color:#aaa;">
<li style="margin:5px 0;">Hover over markers for PC details</li>
<li style="margin:5px 0;">Markers placed at related machine location</li>
<li style="margin:5px 0;">Click "View PC" for full information</li>
<li style="margin:5px 0;">Red markers need attention!</li>
</ul>
</div>
</div>
</div>
</div>
</div><!--End Row-->
<!-- PCs without machine relationships (can't be mapped) -->
<div class="row" style="margin-top:20px;">
<div class="col-12">
<div class="card">
<div class="card-header" style="background: linear-gradient(45deg, #6c757d, #495057); color: white; cursor:pointer;" onclick="toggleUnmappedTable()">
<i class="zmdi zmdi-alert-circle"></i> PCs That Cannot Be Mapped (No Relationship or No Location)
<span id="unmappedToggle" style="float:right;"><i class="zmdi zmdi-chevron-down"></i></span>
</div>
<div id="unmappedTable" class="card-body" style="display:none; padding:0;">
<p style="padding:15px 15px 0 15px; font-size:12px; color:#aaa;">
These PCs have high uptime but cannot be placed on the map because they either have no machine relationship,
or their related machine has no map coordinates. <span style="color:#FFC107;">*</span> = has relationship but machine needs location.
</p>
<div class="table-responsive">
<table class="table table-sm table-hover" style="margin:0;">
<thead>
<tr>
<th>Hostname</th>
<th>Uptime (Days)</th>
<th>Last Boot</th>
<th>Business Unit</th>
<th>Related Machine</th>
<th>Serial Number</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<%
' Query PCs with high uptime that CANNOT be mapped:
' 1. No machine relationship at all, OR
' 2. Has relationship but equipment has no map coordinates
Dim rsUnmapped, strUnmappedSQL
Dim unmappedDisplayCount
unmappedDisplayCount = 0
strUnmappedSQL = "SELECT " &_
"pc.machineid, " &_
"pc.hostname, " &_
"pc.serialnumber, " &_
"pc.lastboottime, " &_
"DATEDIFF(NOW(), pc.lastboottime) as uptime_days, " &_
"bu.businessunit, " &_
"eq.machinenumber as related_machine " &_
"FROM machines pc " &_
"LEFT JOIN machinerelationships mr ON pc.machineid = mr.related_machineid AND mr.isactive = 1 " &_
"LEFT JOIN machines eq ON mr.machineid = eq.machineid " &_
"LEFT JOIN businessunits bu ON pc.businessunitid = bu.businessunitid " &_
"WHERE pc.pctypeid IS NOT NULL " &_
"AND pc.isactive = 1 " &_
"AND pc.lastboottime IS NOT NULL " &_
"AND DATEDIFF(NOW(), pc.lastboottime) >= " & minUptime & " " &_
"AND (mr.relationshipid IS NULL OR eq.mapleft IS NULL OR eq.maptop IS NULL) " &_
"ORDER BY uptime_days DESC"
Set rsUnmapped = objConn.Execute(strUnmappedSQL)
Do While Not rsUnmapped.EOF
unmappedDisplayCount = unmappedDisplayCount + 1
Dim umHostname, umUptime, umLastBoot, umBU, umSerial, umId, umColor
umId = rsUnmapped("machineid")
umHostname = rsUnmapped("hostname") & ""
If IsNull(rsUnmapped("uptime_days")) Then
umUptime = 0
Else
umUptime = CLng(rsUnmapped("uptime_days"))
End If
umLastBoot = rsUnmapped("lastboottime")
umSerial = rsUnmapped("serialnumber") & ""
If IsNull(rsUnmapped("businessunit")) Then
umBU = "-"
Else
umBU = rsUnmapped("businessunit") & ""
End If
Dim umRelatedMachine
If IsNull(rsUnmapped("related_machine")) Then
umRelatedMachine = ""
Else
umRelatedMachine = rsUnmapped("related_machine") & ""
End If
' Color based on uptime
If umUptime >= 90 Then
umColor = "#F44336"
ElseIf umUptime >= 60 Then
umColor = "#FF9800"
ElseIf umUptime >= 30 Then
umColor = "#FFC107"
Else
umColor = "#4CAF50"
End If
%>
<tr>
<td><a href="./displaypc.asp?machineid=<%=umId%>" target="_blank"><%=Server.HTMLEncode(umHostname)%></a></td>
<td><span style="color:<%=umColor%>; font-weight:bold;"><%=umUptime%></span></td>
<td><%=umLastBoot%></td>
<td><%=Server.HTMLEncode(umBU)%></td>
<td><%If umRelatedMachine <> "" Then%><span style="color:#FFC107;" title="Has relationship but machine has no map location"><%=Server.HTMLEncode(umRelatedMachine)%> *</span><%Else%><span style="color:#aaa;">None</span><%End If%></td>
<td><%=Server.HTMLEncode(umSerial)%></td>
<td><a href="./displaypc.asp?machineid=<%=umId%>" target="_blank" class="btn btn-sm btn-outline-info">View</a></td>
</tr>
<%
rsUnmapped.MoveNext
Loop
rsUnmapped.Close
Set rsUnmapped = Nothing
%>
</tbody>
</table>
</div>
<div style="padding:10px 15px; background:#2a2a2a; font-size:12px; color:#aaa;">
Total: <strong style="color:#fff;"><%=unmappedDisplayCount%></strong> PCs without machine relationships
</div>
</div>
</div>
</div>
</div>
</div><!--End content-wrapper-->
<!--Start Back To Top Button-->
<a href="javaScript:void();" class="back-to-top"><i class="fa fa-angle-double-up"></i> </a>
<!--End Back To Top Button-->
<!--Start footer-->
<footer class="footer">
</div>
</footer>
<!--End footer-->
</div><!--End wrapper-->
<!-- Bootstrap core JavaScript-->
<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/popper.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/plugins/simplebar/js/simplebar.js"></script>
<script src="assets/js/sidebar-menu.js"></script>
<script src="assets/js/app-script.js"></script>
<style>
#map {
width: 100%;
height: calc(100vh - 250px);
min-height: 600px;
background-color: #1a1a1a;
}
.leaflet-control-zoom a {
background-color: #2a2a2a !important;
color: #fff !important;
border-color: #444 !important;
}
.leaflet-control-zoom a:hover {
background-color: #3a3a3a !important;
}
.leaflet-bar {
border: 1px solid #444 !important;
}
.leaflet-popup-content-wrapper {
background: #1f1f1f !important;
color: #fff !important;
box-shadow: 0 3px 14px rgba(0,0,0,0.6) !important;
border-radius: 4px !important;
padding: 0 !important;
}
.leaflet-popup-content {
margin: 0 !important;
}
.leaflet-popup-tip-container {
display: none !important;
}
.leaflet-popup-close-button {
color: #fff !important;
font-size: 24px !important;
padding: 4px 8px 0 0 !important;
}
.leaflet-control-attribution {
display: none !important;
}
.uptime-pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.8; }
}
</style>
<script>
// Get current theme
var bodyClass = document.body.className;
var themeMatch = bodyClass.match(/bg-theme(\d+)/);
var theme = themeMatch ? 'bg-theme' + themeMatch[1] : 'bg-theme1';
var themeConfig = {
'bg-theme1': { bg: '#2a2a2a', filter: 'brightness(0.7) contrast(1.1)', gradient: 'linear-gradient(45deg, #ff6b6b, #ee5a24)' },
'bg-theme7': { bg: '#0c675e', filter: 'brightness(0.8) contrast(1.1) hue-rotate(-10deg)', gradient: 'linear-gradient(45deg, #ff6b6b, #ee5a24)' },
'bg-theme11': { bg: '#1565C0', filter: 'brightness(0.85) contrast(1.05) hue-rotate(-5deg)', gradient: 'linear-gradient(45deg, #ff6b6b, #ee5a24)' }
};
var config = themeConfig[theme] || { bg: '#1a1a1a', filter: 'brightness(0.7) contrast(1.1)', gradient: 'linear-gradient(45deg, #ff6b6b, #ee5a24)' };
document.getElementById('map').style.backgroundColor = config.bg;
var map = L.map('map', {
crs: L.CRS.Simple,
minZoom: -3
});
var bounds = [[0,0], [2550,3300]];
var lightThemes = ['bg-theme11', 'bg-theme13'];
var mapImage = lightThemes.includes(theme) ? './images/sitemap2025-light.png' : './images/sitemap2025-dark.png';
var image = L.imageOverlay(mapImage, bounds);
image.on('load', function() {
var imgElement = this.getElement();
if (imgElement) {
imgElement.style.filter = config.filter;
}
});
image.addTo(map);
var center = [1275, 1650];
map.setView(center, -2.3);
// Store PC markers
var pcMarkers = [];
// Uptime color function - single color for all (reboot needed)
function getUptimeColor(days) {
return '#F44336'; // Red - all PCs need reboot
}
function getUptimeLabel(days) {
return 'Needs Reboot';
}
<%
' First, get count of PCs without relationships (for stats display)
Dim rsUnmappedCount, unmappedCount
unmappedCount = 0
Set rsUnmappedCount = objConn.Execute("SELECT COUNT(*) as cnt FROM machines pc " &_
"LEFT JOIN machinerelationships mr ON pc.machineid = mr.related_machineid AND mr.isactive = 1 " &_
"LEFT JOIN machines eq ON mr.machineid = eq.machineid " &_
"WHERE pc.pctypeid IS NOT NULL AND pc.isactive = 1 " &_
"AND pc.lastboottime IS NOT NULL " &_
"AND DATEDIFF(NOW(), pc.lastboottime) >= " & minUptime & " " &_
"AND (mr.relationshipid IS NULL OR eq.mapleft IS NULL OR eq.maptop IS NULL)")
If Not rsUnmappedCount.EOF Then
unmappedCount = CLng(rsUnmappedCount("cnt"))
End If
rsUnmappedCount.Close
Set rsUnmappedCount = Nothing
' Query PCs with machine relationships and high uptime
' Use the RELATED MACHINE's map coordinates since PCs don't have their own
Dim strSQL, rs
Dim pcId, pcHostname, pcUptime, lastBoot
Dim eqId, eqNumber, eqMapLeft, eqMapTop, eqType
Dim businessunitid, businessunit
strSQL = "SELECT " &_
"pc.machineid as pc_id, " &_
"pc.hostname as pc_hostname, " &_
"pc.lastboottime, " &_
"DATEDIFF(NOW(), pc.lastboottime) as uptime_days, " &_
"eq.machineid as eq_id, " &_
"eq.machinenumber as eq_number, " &_
"eq.mapleft as eq_mapleft, " &_
"eq.maptop as eq_maptop, " &_
"mt.machinetype as eq_type, " &_
"pc.businessunitid, " &_
"bu.businessunit " &_
"FROM machines pc " &_
"INNER JOIN machinerelationships mr ON pc.machineid = mr.related_machineid AND mr.isactive = 1 " &_
"INNER JOIN machines eq ON mr.machineid = eq.machineid " &_
"LEFT JOIN models mo ON eq.modelnumberid = mo.modelnumberid " &_
"LEFT JOIN machinetypes mt ON mo.machinetypeid = mt.machinetypeid " &_
"LEFT JOIN businessunits bu ON pc.businessunitid = bu.businessunitid " &_
"WHERE pc.pctypeid IS NOT NULL " &_
"AND pc.isactive = 1 " &_
"AND pc.lastboottime IS NOT NULL " &_
"AND DATEDIFF(NOW(), pc.lastboottime) >= " & minUptime & " " &_
"AND eq.mapleft IS NOT NULL " &_
"AND eq.maptop IS NOT NULL " &_
"ORDER BY uptime_days DESC"
Set rs = objConn.Execute(strSQL)
Dim pcCount
pcCount = 0
Do While Not rs.EOF
pcCount = pcCount + 1
pcId = rs("pc_id")
pcHostname = rs("pc_hostname") & ""
pcUptime = rs("uptime_days")
lastBoot = rs("lastboottime")
eqId = rs("eq_id")
eqNumber = rs("eq_number") & ""
eqMapLeft = rs("eq_mapleft")
eqMapTop = 2550 - rs("eq_maptop") ' Flip Y coordinate
If Not IsNull(rs("eq_type")) Then
eqType = rs("eq_type")
Else
eqType = "Unknown"
End If
If Not IsNull(rs("businessunitid")) Then
businessunitid = rs("businessunitid")
Else
businessunitid = 0
End If
If Not IsNull(rs("businessunit")) Then
businessunit = rs("businessunit")
Else
businessunit = "N/A"
End If
%>
(function() {
var pcId = '<%=pcId%>';
var pcHostname = '<%=Server.HTMLEncode(pcHostname)%>';
var uptimeDays = <%=pcUptime%>;
var lastBoot = '<%=lastBoot%>';
var eqId = '<%=eqId%>';
var eqNumber = '<%=Server.HTMLEncode(eqNumber)%>';
var eqType = '<%=Server.HTMLEncode(eqType)%>';
var businessUnitId = '<%=businessunitid%>';
var businessUnit = '<%=Server.HTMLEncode(businessunit)%>';
var color = getUptimeColor(uptimeDays);
var uptimeLabel = getUptimeLabel(uptimeDays);
// Create custom marker icon
var icon = L.divIcon({
html: '<div style="background:' + color + '; width:14px; height:14px; border-radius:50%; border:2px solid #fff; box-shadow:0 2px 5px rgba(0,0,0,0.5);"></div>',
iconSize: [14, 14],
iconAnchor: [7, 7],
popupAnchor: [0, -5],
className: 'custom-marker'
});
var marker = L.marker([<%=eqMapTop%>, <%=eqMapLeft%>], {
title: pcHostname + ' (' + uptimeDays + ' days)',
icon: icon,
pcId: pcId,
uptimeDays: uptimeDays
}).addTo(map);
// Store marker with searchable data
pcMarkers.push({
marker: marker,
pcId: pcId,
uptimeDays: uptimeDays,
businessUnitId: businessUnitId,
searchData: {
hostname: pcHostname.toLowerCase(),
machine: eqNumber.toLowerCase(),
bu: businessUnit.toLowerCase()
}
});
// Popup on hover
var popupTimeout;
marker.on('mouseover', function() {
clearTimeout(popupTimeout);
this.openPopup();
});
marker.on('mouseout', function(e) {
popupTimeout = setTimeout(function() {
marker.closePopup();
}, 800);
});
var pcUrl = './displaypc.asp?machineid=' + pcId;
var eqUrl = './displaymachine.asp?machineid=' + eqId;
var popupContent = '<div style="background:#1f1f1f; color:#fff; min-width:280px; border-radius:4px; overflow:hidden;">' +
'<div style="background:' + config.gradient + '; padding:10px 15px; border-bottom:1px solid #444;">' +
'<h6 style="margin:0; color:#fff; font-size:14px;"><i class="zmdi zmdi-desktop-mac"></i> ' + pcHostname + '</h6>' +
'</div>' +
'<div style="padding:10px 15px; font-size:12px;">' +
'<div style="margin:8px 0; padding:10px; background:' + color + '22; border-left:3px solid ' + color + '; border-radius:0 4px 4px 0;">' +
'<strong style="color:' + color + '; font-size:16px;">' + uptimeDays + ' days uptime</strong>' +
'<div style="color:#aaa; font-size:11px; margin-top:3px;">Status: ' + uptimeLabel + '</div>' +
'</div>' +
'<div style="margin:5px 0;"><strong style="color:#aaa;">Last Boot:</strong> <span style="color:#fff;">' + lastBoot + '</span></div>' +
'<div style="margin:5px 0;"><strong style="color:#aaa;">Related Machine:</strong> <a href="' + eqUrl + '" target="_blank" style="color:#4fc3f7;">' + eqNumber + '</a> (' + eqType + ')</div>' +
(businessUnit !== 'N/A' ? '<div style="margin:5px 0;"><strong style="color:#aaa;">Business Unit:</strong> <span style="color:#fff;">' + businessUnit + '</span></div>' : '') +
'</div>' +
'<div style="padding:10px 15px; border-top:1px solid #444; text-align:center;">' +
'<a href="' + pcUrl + '" style="display:inline-block; background:' + config.gradient + '; color:#fff; padding:8px 18px; border-radius:4px; text-decoration:none; font-size:13px; font-weight:500;" target="_blank"><i class="zmdi zmdi-desktop-mac"></i> View PC</a>' +
'</div>' +
'</div>';
marker.bindPopup(popupContent);
})();
<%
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
objConn.Close
%>
// Update stats
document.getElementById('pcCount').innerHTML =
'<div style="margin:5px 0;"><i class="zmdi zmdi-pin"></i> <strong><%=pcCount%></strong> PCs on map</div>' +
'<div style="margin:5px 0;"><i class="zmdi zmdi-alert-circle" style="color:#6c757d;"></i> <strong><%=unmappedCount%></strong> PCs without location</div>' +
'<div style="margin:5px 0; font-size:12px; color:#aaa;">Total: <%=pcCount + unmappedCount%> high-uptime PCs</div>';
// Toggle unmapped PCs table
function toggleUnmappedTable() {
var table = document.getElementById('unmappedTable');
var toggle = document.getElementById('unmappedToggle');
if (table.style.display === 'none') {
table.style.display = 'block';
toggle.innerHTML = '<i class="zmdi zmdi-chevron-up"></i>';
} else {
table.style.display = 'none';
toggle.innerHTML = '<i class="zmdi zmdi-chevron-down"></i>';
}
}
// Filter functionality
function applyFilters() {
var selectedBU = document.getElementById('businessUnitFilter').value;
var searchTerm = document.getElementById('pcSearch').value.toLowerCase().trim();
var minUptimeVal = parseInt(document.getElementById('uptimeFilter').value);
var visibleCount = 0;
pcMarkers.forEach(function(item) {
var buMatch = (selectedBU === 'all' || item.businessUnitId == selectedBU);
var uptimeMatch = item.uptimeDays >= minUptimeVal;
var searchMatch = true;
if (searchTerm !== '') {
searchMatch = item.searchData.hostname.indexOf(searchTerm) > -1 ||
item.searchData.machine.indexOf(searchTerm) > -1 ||
item.searchData.bu.indexOf(searchTerm) > -1;
}
if (buMatch && uptimeMatch && searchMatch) {
item.marker.setOpacity(1);
visibleCount++;
} else {
item.marker.setOpacity(0.15);
}
});
// Update visible count
document.getElementById('pcCount').innerHTML =
'<div style="margin:5px 0;"><i class="zmdi zmdi-desktop-mac"></i> <strong>' + visibleCount + '</strong> PCs visible</div>' +
'<div style="margin:5px 0; font-size:12px; color:#aaa;">Filtered from <%=pcCount%> total</div>';
}
// Uptime filter changes page
document.getElementById('uptimeFilter').addEventListener('change', function() {
window.location.href = '?minuptime=' + this.value;
});
// Listen to filter changes
document.getElementById('businessUnitFilter').addEventListener('change', applyFilters);
// Listen to search input with debouncing
var searchTimeout;
document.getElementById('pcSearch').addEventListener('input', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(applyFilters, 300);
});
</script>
</body>
</html>

View File

@@ -130,8 +130,8 @@
checkinTime = Month(rs("checkin_time")) & "/" & Day(rs("checkin_time")) & "/" & Year(rs("checkin_time")) & " " & FormatDateTime(rs("checkin_time"), 3)
statusClass = ""
Else
checkinTime = "<span class='badge badge-warning'>Still Out</span>"
statusClass = "table-warning"
checkinTime = "<span class='badge badge-dark'>Still Out</span>"
statusClass = ""
End If
' Format duration

329
usblabelbatch.asp Normal file
View File

@@ -0,0 +1,329 @@
<%@ Language=VBScript %>
<%
Option Explicit
Dim objConn, rs
%>
<!DOCTYPE html>
<html>
<head>
<!--#include file="./includes/sql.asp"-->
<title>Batch Print USB Barcode Labels</title>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
<style>
/* ULINE S-20135: 8.5x11 sheet, 3x3 labels, 2 cols x 3 rows */
/* Each 3x3 label holds 3x4 grid of 1x0.75 mini-labels (12 per cell) */
@page { size: letter; margin: 0; }
body { font-family: Arial, sans-serif; background: #f0f0f0; margin: 0; padding: 20px; }
.no-print { margin-bottom: 20px; }
.controls { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
.controls h3 { margin-top: 0; }
.print-btn { padding: 10px 30px; font-size: 16px; cursor: pointer; background: #667eea; color: white; border: none; border-radius: 5px; margin-right: 10px; }
.print-btn:disabled { background: #ccc; cursor: not-allowed; }
.clear-btn { padding: 10px 20px; font-size: 14px; cursor: pointer; background: #dc3545; color: white; border: none; border-radius: 5px; margin-right: 10px; }
.select-all-btn { padding: 10px 20px; font-size: 14px; cursor: pointer; background: #28a745; color: white; border: none; border-radius: 5px; }
.back-btn { padding: 10px 20px; font-size: 14px; cursor: pointer; background: #6c757d; color: white; border: none; border-radius: 5px; text-decoration: none; margin-left: 10px; }
.usb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; max-height: 300px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; background: #fafafa; }
.usb-item { display: flex; align-items: center; padding: 8px; background: white; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
.usb-item:hover { background: #f0f0f0; }
.usb-item.selected { background: #e7f1ff; border-color: #667eea; }
.usb-item input { margin-right: 10px; }
.usb-item label { cursor: pointer; flex: 1; }
.usb-item .alias { font-size: 11px; color: #666; }
.selected-count { font-weight: bold; margin: 10px 0; }
.selected-count .count { color: #667eea; }
.selected-count .pages { color: #28a745; }
.sheets-container { display: flex; flex-direction: column; gap: 20px; }
.print-sheet { width: 8.5in; height: 11in; background: white; margin: 0 auto; position: relative; border: 1px solid #ccc; page-break-after: always; }
.print-sheet:last-child { page-break-after: auto; }
.sheet-label { position: absolute; top: -25px; left: 0; font-size: 12px; color: #666; }
.label-cell {
width: 3in;
height: 3in;
position: absolute;
box-sizing: border-box;
border: 1px dashed #ccc;
overflow: hidden;
}
.label-cell.has-content { border: 1px solid #667eea; }
.label-cell.empty { background: #fafafa; }
/* Label cell positions */
.cell-1 { top: 0.875in; left: 1.1875in; }
.cell-2 { top: 0.875in; left: 4.3125in; }
.cell-3 { top: 4in; left: 1.1875in; }
.cell-4 { top: 4in; left: 4.3125in; }
.cell-5 { top: 7.125in; left: 1.1875in; }
.cell-6 { top: 7.125in; left: 4.3125in; }
.mini-grid { display: grid; grid-template-columns: repeat(3, 1in); grid-template-rows: repeat(4, 0.75in); width: 3in; height: 3in; }
.mini-label { width: 1in; height: 0.75in; display: flex; flex-direction: column; align-items: center; justify-content: center; box-sizing: border-box; padding: 0.02in; border: 1px dotted #ddd; overflow: hidden; }
.mini-label.filled { border: 1px solid #999; }
.mini-label.empty { background: #f8f8f8; border: 1px dotted #eee; }
.barcode-container { text-align: center; line-height: 0; }
.barcode-container svg { max-width: 0.9in; height: 24px; }
.serial-text { font-size: 6pt; font-weight: bold; font-family: monospace; text-align: center; margin-top: 1px; letter-spacing: 0.3px; }
.empty-cell-text { color: #ccc; font-size: 12px; display: flex; align-items: center; justify-content: center; height: 100%; }
@media print {
body { padding: 0; margin: 0; background: white; }
.no-print { display: none; }
.sheets-container { gap: 0; }
.print-sheet { border: none; margin: 0; width: 8.5in; height: 11in; overflow: hidden; }
.sheet-label { display: none; }
.label-cell { border: none !important; }
.label-cell.empty { visibility: hidden; }
.mini-label { border: 1px dotted #ccc !important; }
.mini-label.empty { visibility: hidden; }
}
</style>
</head>
<body>
<%
Dim strSQL
strSQL = "SELECT m.machineid, m.serialnumber, m.alias FROM machines m WHERE m.machinetypeid = 44 AND m.isactive = 1 ORDER BY m.serialnumber ASC"
Set rs = objConn.Execute(strSQL)
%>
<div class="no-print">
<div class="controls">
<h3>Batch Print USB Barcode Labels</h3>
<p>Select USB devices to print (72 labels per page - 6 ULINE labels x 12 mini-labels each, cut after printing):</p>
<div class="usb-grid">
<%
Dim displayName
Do While Not rs.EOF
displayName = rs("serialnumber") & ""
%>
<div class="usb-item" onclick="toggleUSB(this, <%=rs("machineid")%>, '<%=Server.HTMLEncode(Replace(rs("serialnumber") & "", "'", "\'"))%>', '<%=Server.HTMLEncode(Replace(rs("alias") & "", "'", "\'"))%>')">
<input type="checkbox" id="usb-<%=rs("machineid")%>">
<label>
<strong><code><%=Server.HTMLEncode(displayName)%></code></strong>
<div class="alias"><%=Server.HTMLEncode(rs("alias") & "")%></div>
</label>
</div>
<%
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
objConn.Close
%>
</div>
<div class="selected-count">
Selected: <span class="count" id="selectedCount">0</span> USB devices
(<span class="pages" id="pageCount">0</span> pages)
<label style="margin-left: 20px;">Start at cell:
<select id="startCell" onchange="updateSheets()" style="padding: 5px; font-size: 14px;">
<option value="1">1 - Top Left</option>
<option value="2">2 - Top Right</option>
<option value="3">3 - Middle Left</option>
<option value="4">4 - Middle Right</option>
<option value="5">5 - Bottom Left</option>
<option value="6">6 - Bottom Right</option>
</select>
</label>
</div>
<button class="print-btn" id="printBtn" onclick="window.print()" disabled>Print Labels</button>
<button class="clear-btn" onclick="clearSelection()">Clear All</button>
<button class="select-all-btn" onclick="selectAll()">Select All</button>
<a href="./displayusb.asp" class="back-btn">Back to USB Devices</a>
</div>
</div>
<div class="sheets-container" id="sheetsContainer">
</div>
<script>
var selectedUSBs = [];
var allUSBs = [];
var MINI_LABELS_PER_CELL = 12;
var CELLS_PER_PAGE = 6;
document.querySelectorAll('.usb-item').forEach(function(el) {
var onclick = el.getAttribute('onclick');
var match = onclick.match(/toggleUSB\(this, (\d+), '([^']*)', '([^']*)'\)/);
if (match) {
allUSBs.push({
id: parseInt(match[1]),
serial: match[2].replace(/\\'/g, "'"),
alias: match[3].replace(/\\'/g, "'"),
element: el
});
}
});
function toggleUSB(el, id, serial, alias) {
var checkbox = el.querySelector('input[type="checkbox"]');
var index = selectedUSBs.findIndex(function(u) { return u.id === id; });
if (index > -1) {
selectedUSBs.splice(index, 1);
checkbox.checked = false;
el.classList.remove('selected');
} else {
selectedUSBs.push({ id: id, serial: serial, alias: alias });
checkbox.checked = true;
el.classList.add('selected');
}
updateSheets();
}
function clearSelection() {
selectedUSBs = [];
document.querySelectorAll('.usb-item').forEach(function(el) {
el.classList.remove('selected');
el.querySelector('input[type="checkbox"]').checked = false;
});
updateSheets();
}
function selectAll() {
selectedUSBs = [];
allUSBs.forEach(function(u) {
selectedUSBs.push({ id: u.id, serial: u.serial, alias: u.alias });
u.element.classList.add('selected');
u.element.querySelector('input[type="checkbox"]').checked = true;
});
updateSheets();
}
function updateSheets() {
var numUSBs = selectedUSBs.length;
var startCell = parseInt(document.getElementById('startCell').value) || 1;
// Calculate cells needed, accounting for skipped cells on first page
var skippedCells = startCell - 1;
var numCells = Math.ceil(numUSBs / MINI_LABELS_PER_CELL);
var totalCellsNeeded = numCells + skippedCells;
var numPages = Math.ceil(totalCellsNeeded / CELLS_PER_PAGE) || 0;
if (numUSBs === 0) numPages = 0;
document.getElementById('selectedCount').textContent = numUSBs;
document.getElementById('pageCount').textContent = numPages;
document.getElementById('printBtn').disabled = numUSBs === 0;
var container = document.getElementById('sheetsContainer');
container.innerHTML = '';
if (numUSBs === 0) {
return;
}
var usbIndex = 0;
for (var page = 0; page < numPages; page++) {
var sheet = document.createElement('div');
sheet.className = 'print-sheet';
sheet.style.position = 'relative';
var sheetLabel = document.createElement('div');
sheetLabel.className = 'sheet-label';
sheetLabel.textContent = 'Page ' + (page + 1) + ' of ' + numPages;
sheet.appendChild(sheetLabel);
for (var cellNum = 1; cellNum <= CELLS_PER_PAGE; cellNum++) {
var cell = document.createElement('div');
// On first page, skip cells before startCell
var skipThisCell = (page === 0 && cellNum < startCell);
var cellHasContent = !skipThisCell && usbIndex < numUSBs;
cell.className = 'label-cell cell-' + cellNum + (cellHasContent ? ' has-content' : ' empty');
if (cellHasContent) {
var miniGrid = document.createElement('div');
miniGrid.className = 'mini-grid';
for (var miniPos = 0; miniPos < MINI_LABELS_PER_CELL; miniPos++) {
var usb = selectedUSBs[usbIndex];
var miniLabel = document.createElement('div');
miniLabel.className = 'mini-label' + (usb ? ' filled' : ' empty');
if (usb) {
var barcodeId = 'bc-' + page + '-' + cellNum + '-' + miniPos;
miniLabel.innerHTML = '<div class="barcode-container"><svg id="' + barcodeId + '"></svg></div><div class="serial-text">' + escapeHtml(usb.serial) + '</div>';
usbIndex++;
}
miniGrid.appendChild(miniLabel);
}
cell.appendChild(miniGrid);
} else {
cell.innerHTML = '<div class="empty-cell-text">Empty</div>';
}
sheet.appendChild(cell);
}
container.appendChild(sheet);
}
setTimeout(generateAllBarcodes, 50);
}
function generateAllBarcodes() {
var usbIndex = 0;
var numUSBs = selectedUSBs.length;
var startCell = parseInt(document.getElementById('startCell').value) || 1;
var skippedCells = startCell - 1;
var numCells = Math.ceil(numUSBs / MINI_LABELS_PER_CELL);
var totalCellsNeeded = numCells + skippedCells;
var numPages = Math.ceil(totalCellsNeeded / CELLS_PER_PAGE) || 0;
usbIndex = 0;
for (var page = 0; page < numPages; page++) {
for (var cellNum = 1; cellNum <= CELLS_PER_PAGE; cellNum++) {
// Skip cells before startCell on first page
if (page === 0 && cellNum < startCell) continue;
for (var miniPos = 0; miniPos < MINI_LABELS_PER_CELL; miniPos++) {
if (usbIndex < numUSBs) {
var usb = selectedUSBs[usbIndex];
var barcodeId = 'bc-' + page + '-' + cellNum + '-' + miniPos;
var el = document.getElementById(barcodeId);
if (el) {
try {
JsBarcode('#' + barcodeId, usb.serial, {
format: "CODE128",
width: 1,
height: 22,
displayValue: false,
margin: 0,
background: "transparent"
});
} catch(e) {
console.error('Barcode error:', usb.serial, e);
}
}
usbIndex++;
}
}
}
}
}
function escapeHtml(text) {
if (!text) return '';
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
</script>
</body>
</html>

195
usbsingle.asp Normal file
View File

@@ -0,0 +1,195 @@
<%@ Language=VBScript %>
<%
Option Explicit
Dim objConn, rs
%>
<!DOCTYPE html>
<html>
<head>
<!--#include file="./includes/sql.asp"-->
<title>Print USB Barcode Label</title>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
<style>
/* ULINE S-20135: 8.5x11 sheet, 3x3 labels, 2 cols x 3 rows */
/* Each 3x3 label holds 3x4 grid of 1x0.75 mini-labels (12 per cell) */
@page { size: letter; margin: 0; }
body { font-family: Arial, sans-serif; background: #f0f0f0; margin: 0; padding: 20px; }
.no-print { margin-bottom: 20px; text-align: center; }
.print-btn { padding: 10px 30px; font-size: 16px; cursor: pointer; background: #667eea; color: white; border: none; border-radius: 5px; margin: 5px; }
.back-btn { padding: 10px 30px; font-size: 16px; cursor: pointer; background: #6c757d; color: white; border: none; border-radius: 5px; margin: 5px; text-decoration: none; }
.position-select { padding: 8px; font-size: 14px; margin-left: 10px; }
.copies-input { padding: 6px; font-size: 14px; width: 60px; text-align: center; }
.print-sheet { width: 8.5in; height: 11in; background: white; margin: 0 auto; position: relative; border: 1px solid #ccc; }
.label-cell {
width: 3in;
height: 3in;
position: absolute;
box-sizing: border-box;
border: 1px dashed #ccc;
overflow: hidden;
}
.label-cell.active { border: 2px solid #667eea; }
.label-cell.inactive { border: 1px dashed #ccc; }
/* Label cell positions */
.cell-1 { top: 0.875in; left: 1.1875in; }
.cell-2 { top: 0.875in; left: 4.3125in; }
.cell-3 { top: 4in; left: 1.1875in; }
.cell-4 { top: 4in; left: 4.3125in; }
.cell-5 { top: 7.125in; left: 1.1875in; }
.cell-6 { top: 7.125in; left: 4.3125in; }
.mini-grid { display: grid; grid-template-columns: repeat(3, 1in); grid-template-rows: repeat(4, 0.75in); width: 3in; height: 3in; }
.mini-label { width: 1in; height: 0.75in; display: flex; flex-direction: column; align-items: center; justify-content: center; box-sizing: border-box; padding: 0.02in; border: 1px dotted #ddd; overflow: hidden; }
.mini-label.filled { border: 1px solid #999; }
.mini-label.empty { background: #f8f8f8; border: 1px dotted #eee; }
.barcode-container { text-align: center; line-height: 0; }
.barcode-container svg { max-width: 0.9in; height: 24px; }
.serial-text { font-size: 6pt; font-weight: bold; font-family: monospace; text-align: center; margin-top: 1px; letter-spacing: 0.3px; }
@media print {
body { padding: 0; margin: 0; background: white; }
.no-print { display: none; }
.print-sheet { border: none; margin: 0; width: 8.5in; height: 11in; overflow: hidden; }
.label-cell { border: none !important; }
.label-cell.inactive { visibility: hidden; }
.mini-label { border: 1px dotted #ccc !important; }
.mini-label.empty { visibility: hidden; }
}
</style>
</head>
<body>
<%
Dim serialNumber, usbAlias, machineid, strSQL
serialNumber = Request.QueryString("serial")
machineid = Request.QueryString("machineid")
If machineid <> "" And IsNumeric(machineid) Then
strSQL = "SELECT serialnumber, alias FROM machines WHERE machineid = " & CLng(machineid) & " AND machinetypeid = 44"
Set rs = objConn.Execute(strSQL)
If Not rs.EOF Then
serialNumber = rs("serialnumber") & ""
usbAlias = rs("alias") & ""
End If
rs.Close
Set rs = Nothing
End If
objConn.Close
serialNumber = Trim(serialNumber)
If serialNumber = "" Then serialNumber = "00000000"
%>
<div class="no-print">
<h2>USB Barcode Label</h2>
<p>Serial: <strong><code><%=Server.HTMLEncode(serialNumber)%></code></strong>
<% If usbAlias <> "" Then %> (<%=Server.HTMLEncode(usbAlias)%>)<% End If %>
</p>
<button class="print-btn" onclick="window.print()">Print Label</button>
<a href="./displayusb.asp" class="back-btn">Back to USB Devices</a>
<div style="margin-top: 15px;">
<label>Position:
<select class="position-select" id="posSelect" onchange="updateLayout()">
<option value="1">1 - Top Left</option>
<option value="2">2 - Top Right</option>
<option value="3">3 - Middle Left</option>
<option value="4">4 - Middle Right</option>
<option value="5">5 - Bottom Left</option>
<option value="6">6 - Bottom Right</option>
</select>
</label>
<label style="margin-left: 20px;">Copies (1-12):
<input type="number" class="copies-input" id="copiesInput" value="12" min="1" max="12" onchange="updateLayout()">
</label>
</div>
<p style="color: #666; font-size: 12px; margin-top: 10px;">Prints on ULINE S-20135 label sheet (3"x3" cells). Each cell holds up to 12 mini-labels (1"x0.75").</p>
</div>
<div class="print-sheet">
<div class="label-cell cell-1 active" id="cell-1"></div>
<div class="label-cell cell-2 inactive" id="cell-2"></div>
<div class="label-cell cell-3 inactive" id="cell-3"></div>
<div class="label-cell cell-4 inactive" id="cell-4"></div>
<div class="label-cell cell-5 inactive" id="cell-5"></div>
<div class="label-cell cell-6 inactive" id="cell-6"></div>
</div>
<script>
var serialNumber = "<%=Server.HTMLEncode(serialNumber)%>";
function updateLayout() {
var pos = parseInt(document.getElementById('posSelect').value) || 1;
var copies = parseInt(document.getElementById('copiesInput').value) || 12;
if (copies < 1) copies = 1;
if (copies > 12) copies = 12;
document.getElementById('copiesInput').value = copies;
for (var i = 1; i <= 6; i++) {
var cell = document.getElementById('cell-' + i);
if (i === pos) {
cell.className = 'label-cell cell-' + i + ' active';
cell.innerHTML = buildMiniGrid(copies);
} else {
cell.className = 'label-cell cell-' + i + ' inactive';
cell.innerHTML = '';
}
}
setTimeout(function() {
for (var j = 0; j < copies; j++) {
var barcodeId = 'bc-' + j;
var el = document.getElementById(barcodeId);
if (el) {
try {
JsBarcode('#' + barcodeId, serialNumber, {
format: "CODE128",
width: 1,
height: 22,
displayValue: false,
margin: 0,
background: "transparent"
});
} catch(e) {
console.error('Barcode error:', e);
}
}
}
}, 50);
}
function buildMiniGrid(copies) {
var html = '<div class="mini-grid">';
for (var i = 0; i < 12; i++) {
if (i < copies) {
html += '<div class="mini-label filled">' +
'<div class="barcode-container"><svg id="bc-' + i + '"></svg></div>' +
'<div class="serial-text">' + escapeHtml(serialNumber) + '</div>' +
'</div>';
} else {
html += '<div class="mini-label empty"></div>';
}
}
html += '</div>';
return html;
}
function escapeHtml(text) {
if (!text) return '';
var div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
updateLayout();
</script>
</body>
</html>