Release Notes

v1.8.4

August 20, 2026 Current

Technician Dashboard workorder search now also matches equipment serial numbers. Each workorder row already carried its linked equipment's serial numbers (used to populate the Equipment section of the ticket detail modal), but the search box only checked ticket ID, customer, description, assigned technician, and SCO representative — so a technician who only had a serial number in hand had no way to find the workorder it belonged to. The search box placeholder now reads "Search by Ticket ID, Customer, Description, or Serial Number" to reflect this, and the match applies across the Open, Assigned, and Closed tabs, since all three tabs share the same search logic.

v1.8.3

July 31, 2026

Reworked how warranty request and computer reimage checklist documents are edited from the Linked Documents screen. These are PDF-based documents, and the editor previously showed the PDF in a read-only browser preview next to a separate list of duplicate text fields — a leftover of the fact that a browser can't read values back out of an embedded PDF viewer. Fields are now editable directly in the document preview itself, the same way the AF1297 form already worked, so what you see is what you're editing. Along the way, also fixed the warranty request form's field text rendering as a dull gray/navy instead of black, a doubled/ghosted-text glitch where the preview's background and the editable overlay both drew the same value slightly offset from each other, and a bug where a field placed twice on the same page (the reimage checklist's custodian contact number) only showed one editable copy instead of both. The editor now warns before closing a document with unsaved changes, and a bug where the Save button could get stuck showing unsaved changes after switching to a different document is fixed. Also fixed two related login issues affecting mobile users. The manual login search box was storing your rank alongside your name (e.g. "SMSgt John Doe") once selected, which is exactly the value a phone's password manager would save and later autofill back in — but the rank-prefixed text no longer matched anything, so autofill silently failed to log you in. The search box now keeps just your plain name going forward, and the underlying user search was made tolerant of both a leftover rank prefix (for anyone whose phone already saved the old value) and name order, so a search still resolves correctly either way. On the reimage checklist, the custodian contact field was renamed "Custodian Info" and now shows both name and phone number (e.g. "SMSgt MICHAEL A GATES / 856.723.3608") instead of just a phone number. The field was also widened toward its label to prevent longer names from getting clipped.

v1.8.2

July 31, 2026

Reworked how warranty request and computer reimage checklist documents are edited from the Linked Documents screen. These are PDF-based documents, and the editor previously showed the PDF in a read-only browser preview next to a separate list of duplicate text fields — a leftover of the fact that a browser can't read values back out of an embedded PDF viewer. Fields are now editable directly in the document preview itself, the same way the AF1297 form already worked, so what you see is what you're editing. Along the way, also fixed the warranty request form's field text rendering as a dull gray/navy instead of black, a doubled/ghosted-text glitch where the preview's background and the editable overlay both drew the same value slightly offset from each other, and a bug where a field placed twice on the same page (the reimage checklist's custodian contact number) only showed one editable copy instead of both. The editor now warns before closing a document with unsaved changes, and a bug where the Save button could get stuck showing unsaved changes after switching to a different document is fixed. Also fixed two related login issues affecting mobile users. The manual login search box was storing your rank alongside your name (e.g. "SMSgt John Doe") once selected, which is exactly the value a phone's password manager would save and later autofill back in — but the rank-prefixed text no longer matched anything, so autofill silently failed to log you in. The search box now keeps just your plain name going forward, and the underlying user search was made tolerant of both a leftover rank prefix (for anyone whose phone already saved the old value) and name order, so a search still resolves correctly either way.

v1.8.1

July 29, 2026

Added the ability to enable or disable a user account directly from the admin panel — a Disable User / Enable User button next to Clear PIN on the Edit User modal, and a slashed-user icon in the User Management table showing which accounts are currently disabled. This was the missing GUI-side undo for account lockouts, which previously could only be lifted by editing the database directly. Toggling requires the users.manage permission rather than just general dashboard access, since it's a more sensitive action than editing a profile field, and an admin can't disable their own account while logged in. While building it, found and fixed a real bug: the automatic "lock an account after repeated failed PIN attempts" protection never actually worked. The failed-attempts counter was spelled three different, inconsistent ways across the schema and the code that increments and checks it (FAILEDATTEMTS in the schema, FAILEDATTEMPTS in the tracking logic, FAILEDATTEMPS in a couple of user-creation defaults) — Mongoose silently drops any field an update touches that isn't declared in the schema, so the counter never actually persisted, and the lockout check was always comparing against nothing. All three now refer to the same field, and the lockout threshold — previously hardcoded to 3 — is configurable via a new PIN_RETRIES value in .env, falling back to 3 if it's missing or invalid.

v1.8.0

July 28, 2026

Added a full server-hardening stack — mod_security, mod_evasive, and fail2ban — after enabling Cloudflare's orange-cloud proxying and edge security features on 514sco.com. fail2ban includes a dedicated checkin-auth jail tuned specifically for repeated login/PIN failures, integrated with the Cloudflare API so a ban actually blocks the attacker at Cloudflare's edge — a purely local ban does nothing against Cloudflare-proxied traffic, since every visitor otherwise appears to originate from Cloudflare's own IP ranges. Getting there first meant closing real gaps in the app's own auth code: login/PIN attempts across all five auth surfaces (dashboard, admin, diagnostics, inventory, PACT) are now rate-limited per IP+account, and every attempt is written to a single-line audit log fail2ban's filter matches against. Along the way, found and fixed the admin login endpoint's rate limiter, which had been effectively disabled (1000 requests per window) and offered no real resistance to PIN brute-forcing. Added a new Security page to the admin dashboard (security.view/security.manage permissions, assignable per group) showing every IP currently banned — locally by fail2ban and/or blocked at Cloudflare's edge — with a live countdown to when each local ban expires and a one-click Unban that clears both places at once. It's restricted to trusted LAN/VPN networks at two independent layers, Apache and the app itself, so the page isn't reachable from outside even before a login is attempted, and untrusted networks now get an explained, styled page instead of a bare Forbidden response. A second tab, Activity Log, shows recent login/PIN attempts and network-restriction hits pulled directly from the app's own auth log, with a Ban button on any row for cutting off something suspicious immediately rather than waiting for it to trip fail2ban's own threshold — it flags whichever row is your own current connection so a ban can't be pointed at yourself by mistake. Diagnosed and fixed a real production incident along the way: this server sits behind pfSense running HAProxy in TCP passthrough mode, which causes every Cloudflare-forwarded connection to appear at Apache as coming from the gateway's own address rather than the real visitor. Banning that shared, misattributed address after 5 failed PIN attempts took the entire site offline for an hour, and separately meant the Security page's network allowlist was actually letting outside traffic through rather than blocking it, since the masked gateway address happened to fall inside the trusted range. Both are fixed the same way — Apache now trusts that specific gateway hop and reads the real client IP from Cloudflare's own header, which was arriving correctly the whole time and simply wasn't being consulted. Also fixed two bugs found while testing the new dashboard end-to-end: Cloudflare's API splits single-address blocks into separate IPv4 and IPv6 rule types, and the dashboard was only ever checking for IPv4 — silently hiding any IPv6 block even when it was live and visible in Cloudflare's own console; and the sudoers rule the app needs to read fail2ban's status had never actually been installed, which failed completely silently, so a real ban was invisible on the dashboard with no error shown anywhere. Both failure paths are now logged and surfaced as a visible warning instead of quietly showing an empty list.

v1.7.9

July 24, 2026

Added a Request Documents feature: a technician can now ask a customer for documentation without them ever logging in. Picking "Request documents" from the Communicate page's template dropdown generates a one-time, 48-hour upload link (built from the server's own HOSTNAME, so it always points at the right environment) and drops it into the editable message body. The customer's upload page accepts .pdf/.doc/.docx/.xls/.xlsx/.txt by default (expandable later via .env, no code changes needed), checks each file's real content against its extension (not just the browser's claimed type), and scans it with ClamAV before a technician ever sees it — files that can't be scanned are kept but clearly flagged "Unscanned" rather than silently treated as safe. Submitted files land in a staging area outside any web-servable folder until a technician reviews them from a new "Awaiting Documents" button on Workorder Details (with a pending-count badge), where they can approve a file onto Linked Documents (date/time-stamped to when the customer actually submitted it) or delete it, keeping a short audit record either way. A new documents.review permission gates who can do that review, assignable per group like other features here. Fixed two related issues found while testing on real hardware: a version mismatch where the file-type library's CommonJS release exposes its detector under a different name than expected, and ClamAV scans failing with a permission error when the upload staging folder lived under a restricted home directory — uploaded files and folders are now given explicit, predictable permissions regardless of the server's umask. Renamed "POC" to "Property Custodian" on the Communicate page and made it actually work — it previously just displayed and sent to the customer's own contact info a second time rather than a different recipient. Email Property Custodian now looks up the real equipment custodian on file (same lookup already used to CC custodians on the original check-in confirmation) and defaults unchecked, since notifying the custodian is a deliberate extra step rather than automatic. There's no SMS equivalent — custodian records only ever have email addresses on file, so that option was left out rather than added as something that could never work.

v1.7.8

July 23, 2026

Added a Cage field (5-character CAGE code, auto-uppercased/trimmed, optional) to Computers, iPads, iPhones, and Printers. It's now a pickable field in the Label Creator for all four device categories, and shows up in Inventory Management's asset edit modal with search and sortable-column support; the BECO spreadsheet import maps it in too. Format validation runs in three layers — schema, update route, and client-side check before submit. Along the way, fixed two other bugs: the iPad, iPhone, and Printer schemas were missing several fields the edit form already exposed (Manufacturer, Unit, POC, Capacity, COLOR, IMEI, IMEI2, CSN, Warranty Expiration, etc.), so edits to those fields silently failed to save with no error shown; and iPad/iPhone each had a harmless duplicate Type field, now cleaned up. Also diagnosed and fixed several Zebra label printer quality issues, using a real ZebraDesigner packet capture as ground truth. Darkness now uses the printer's absolute ~SD command instead of the relative ^MD, and the print modal's 0-15 density override now correctly scales to Zebra's native 0-30 range — previously darkness changes could look inconsistent print to print, and the UI's override never reached more than half the printer's real range. QR codes on Zebra prints were noticeably smaller than the same code on a Phomemo print; switched Zebra's QR rendering from its native ^BQ barcode command (a coarse 1-10 magnification scale with no reliable size formula) to a hand-built bitmap using the same QR data as the Phomemo path, for pixel-exact size parity between printer types. The classification banner element (the Secret/Unclassified box) no longer has "IF FOUND / PLEASE CALL <office phone>" welded into it — that text is now separate elements in the Label Creator, so each template can reposition, reword, or drop that callout independently of the classification box.

v1.7.7

July 23, 2026

Refined the digital signature feature added in 1.7.6. Signatures and their date/time stamps now preview directly on the document before saving — drag and resize a signature into place, or redo it, before Save Changes locks it in. Timestamps now show as a boxed date-on-top, time-on-bottom stamp next to each signature. Fixed signatures occasionally not appearing after save, a mislabeled "Signed" button, and a vertical placement offset that could land a manually-placed signature a bit higher than where it was dragged.

v1.7.1

July 22, 2026

Fixed a bug where a corrected AF1297 could still be sent to a customer as a stale image if the Communicate page cached an earlier version. The Communicate page's 'Include AF1297' checkbox is now a full document checklist, matching the EITaaS export page — a technician can select any linked document(s) (AF1297, warranty requests, etc.) to send, not just the 1297. Selected documents go out as real email attachments and, for text messages, as MMS images; templated documents like AF1297 are regenerated fresh at send time rather than reused from a cached file, so edits are always reflected. The initial check-in confirmation send also now regenerates its AF1297 image fresh before sending. Also fixed a bug where printing an equipment label to a Zebra printer failed with a 'typeLabel is not defined' error (Phomemo printing was unaffected).

v1.7.0

July 21, 2026

AF1297 'Issued By'/'Issued To' reversal (for issues like SIPR token pickup where SCO hands equipment to the customer rather than the reverse) is now a 'Reverses Issued By/To' toggle on each issue in the admin Issues manager, replacing a hardcoded list of issue IDs — new issues that need this no longer require a code change. Added per-issue serial number format enforcement: an 'Enforce Serial Format' toggle on each issue, with an optional custom pattern (e.g. a fixed token format) or a fallback to the app's default 6-30 character format; turning enforcement off accepts any serial number. This applies consistently on workorder creation, when adding equipment from Workorder Details, and on the server. Fixed the printed equipment label's phone number overlapping the MAC address, added the missing navigation menu to the Manual Check-In page, and widened two admin dashboard modals (Issue Editor and Issues Management) so their content no longer requires horizontal scrolling.

v1.6.9

July 21, 2026

Added network auto-discovery for label printers: a new Discover Printers button on the admin dashboard scans a configurable subnet for devices listening on port 9100, identifies each one as TSPL (Phomemo) or ZPL (Zebra) by querying it directly, and optionally checks mDNS as a faster path for Zebra printers. Results can be sent straight into the Add Printer form with one click instead of typing IPs in by hand. Also gave Label Sizes the same export/import/bulk-delete tools the printer registry already had — checkboxes, select-all, and Export/Import/Delete Size(s) buttons, using a self-contained .labelsize file — so label sizes can move between the dev and production sites the same way printers do. A new printers.discover permission gates the network scan, grantable independently per group like the other printer permissions.

v1.6.8

July 21, 2026

Added a per-device Settings section to the hamburger menu (above About) with two toggles: Dark Mode and Play Check-In Greeting. Both are stored in the browser's local storage rather than tied to a login, so they're unique to that computer — clearing cookies won't reset them, and an unconfigured device keeps behaving exactly as before (light theme, greeting on). Dark mode is applied as early as possible on page load to avoid a flash of light mode, and reaches every page that includes the hamburger menu via a shared theme stylesheet, with additional overrides added to the check-in, work order creation, work order details, and technician dashboard pages for elements that needed page-specific dark styling. Status, priority, and technician badges keep their normal colors in dark mode since they carry meaning. The greeting toggle sits underneath the existing site-wide greeting setting — both need to be on for the spoken greeting to play on a given device.

v1.6.7

July 21, 2026

Label printing feature complete. Technicians can print equipment labels straight from Workorder Details for Phomemo (TSPL, bitmap-rendered) and Zebra (ZPL, native commands) printers over raw TCP — each label shows device type, QR code, serial, unit, and MAC (computers only), with a per-item Secret/Unclassified toggle. The print modal now includes a live preview before anything is sent to a printer, per-item copies, and per-job DPI/Speed/Density/Invert Colors overrides that apply for that print run only. Printer configuration moved from a static config file into MongoDB, managed from a new Label Printers card on the Admin Dashboard: printers reference reusable named label-size presets, can be duplicated as a starting point for adding a similar printer, and can be exported/imported as .labelprinter files (or bulk-deleted) to make moving configs between the dev and production sites straightforward. Five new permissions back all of this — workOrders.printLabels for using a printer, and printers.create/update/delete/export/import for managing the registry — each grantable independently per group. Also fixed a bug where labels for equipment added to a ticket via the Equipment modal wouldn't show up in Print Labels until the page was refreshed; the print modal now always reads the ticket's current serial numbers instead of a stale copy from when the page first loaded.

v1.6.6

July 19, 2026

Fixed group permission changes silently failing to save. models/schemas/UserGroup.js defines group permissions as a strict, explicitly-listed nested schema, and the recently added workOrders.reassign, workOrders.unassign, and system.admin permissions were never added to it — Mongoose's default strict mode was quietly dropping those fields on every save with no error shown, so checking them in the group editor appeared to work but never actually persisted. Added the missing fields to the schema and verified every permission defined in utils/rbac.js now has a matching schema path, so this class of bug can't happen unnoticed again for any current permission.

v1.6.5

July 19, 2026

Fixed a bug where assigning a work order to a technician could fail with "Selected user does not have permission to be assigned work orders" even after granting every permission visible in the group editor — workOrders.assign was enforced in code but had no checkbox anywhere in the UI, making it impossible to grant. Audited the full permission list against the group editor and added 13 missing permission checkboxes, organized under new category subheadings (Work Orders, Users, Groups, Inventory, System, PACT). Also split the overloaded workOrders.assign permission into three distinct permissions: Be Assigned Work Orders (eligibility to receive an assignment), Assign / Reassign Work Orders (perform the action), and a new Unassign Work Orders permission. Added an Unassign button to the Workorder Details page, next to Assign, disabled whenever a ticket has no assignee, letting a ticket be released back to the pool if the assigned technician can't complete it.

v1.6.4

July 19, 2026

Housekeeping: archived retired linked-document templates (checklist.ejs, ComputerReImage.ejs, and their associated JS/CSS) now that the PDF-based reimage checklist and warranty request workflows replace them — verified first that ComputerReImage-data.js is still a live dependency of the new PDF checklist feature and left it in place. Also traced and archived several other dead files after confirming nothing in the codebase references them: the misspelled templetes/ folder of source forms, the one-off backups/ snapshot folder, middleware/auth.js (superseded by middleware/adminAuth.js), and public/js/workorder_create.old.js. Everything moved to an archive/ folder at the project root rather than being deleted outright, so it can be verified nothing broke before removing it for good.

v1.6.3

July 19, 2026

Added an HP and Dell warranty request workflow as a new linked document type. Editable warranty request PDFs are generated from the original fillable HP or Dell template (selected automatically by manufacturer), with a separate document created per equipment serial number named Warranty_Request-[Serial Number]. Equipment, warranty, and work-order information is populated automatically, including the operating system version (read from SDC_VERSION), the document creator as the primary onsite contact, OFFICE_PHONE_NUMBER as the shared commercial contact number, and technician-specific email and mobile contact details. HP requests are sent to usafEmail and Dell requests to usafEmail1, with the sending technician, office, a configured alternate contact, the primary custodian, and custodian alternates all CC'd (custodian recipients are resolved per-document from that document's equipment serial number), and duplicate recipients are removed automatically. The linked-document email dialog now also supports multiple CC addresses.

v1.6.2

July 19, 2026

Added editable, per-serial computer reimage checklist PDFs as a new linked document type. Checklists are generated from the official fillable PDF template, with one document created per device serial number and named ComputerReImageChecklist-[Serial Number]. Fields are pre-populated from workorder and device lookup data, and the original checklist layout and fillable PDF fields are preserved, so the document editor can open, edit, and save PDF fields directly (with same-origin PDF previews) rather than only plain HTML documents. Also added direct PDF printing and export, and PDF-backed checklists now rasterize correctly to JPG export using Poppler. Existing HTML linked-document behavior is unchanged.

v1.6.1

July 19, 2026

Site-wide visual consistency pass: added the hamburger menu and a clickable home logo to every page that was missing them (Workorder Details, EITaaS, Communicate, Linked Documents, Unmatched Messages triage inbox, and all PACT TECH Refresh pages), so navigation now works the same everywhere. Fixed the PACT TECH Refresh Home page’s hamburger icon rendering invisible — it depends on Font Awesome, which that page (and PIN Verification and Export Confirm) never loaded. Nudged logos on pages like EITaaS so they no longer sit underneath the fixed hamburger icon. Fixed the Change PIN, About, and Virtual CAC modals’ Close buttons, which were cut off or washed-out on any page that also loads Bootstrap (technician dashboard, login, etc.) — traced through three collisions where Bootstrap’s own .modal-content and .btn-close classes silently filled in display, width/height, and opacity properties these custom modals never declared themselves. Also fixed the About modal’s Close button overflowing past the modal’s edge on those same pages. Also added this Release Notes page.

v1.6.0

July 19, 2026

Two-way SMS/MMS and email communication with customers: reply directly from the Communicate page (new MMS/Email/History tabs), inbound Twilio and Mailgun webhooks with signature validation, automatic matching of replies to the correct workorder, and a triage inbox for anything that can’t be auto-matched. Communicate access is now split into separate SMS and Email permissions, assignable per group. Linked documents now show a full date and time (configurable timezone) instead of just a date. AF1297 is generated automatically at workorder creation instead of only on manual request or every notification send. EITaaS export now lets you pick which linked documents to attach, with a select-all toggle. Resending notifications now automatically CCs the equipment custodian(s) and the office box for computer/printer workorders. Added a spoken Microsoft Azure voice greeting on check-in, addressing customers by full rank and name, configurable via GREETING and GREETINGSTATEMENT. Plus several underlying fixes: inbound message matching now correctly requires DOD ID and PIV together, ambiguous matches resolve to the most recently active conversation, outbound email HTML now displays correctly, a data-attribute escaping bug that could break the Communicate page was fixed, and the Content-Security-Policy was updated to allow the greeting audio to play.

v1.5.1

June 14, 2026

Added the PACT Package Explorer: a browsable view of a TECH Refresh’s generated files with workspace summary statistics (rows, files, PDFs, total size, missing files), search with auto-expand across both Package Explorer and TECH Refresh Home, an embedded PDF preview modal, per-file view/download/print actions, download-all and print-all for AF1297 and Checklist packages, and missing-file detection that disables actions for documents that aren’t available.

v1.5.0

June 13, 2026

Groundwork for the Package Explorer feature: generated AF1297 and TECH Refresh Checklist PDFs are now saved to disk individually (not just bundled inside ZIP archives) during package generation, with their file names and paths recorded on the job, so individual PDFs can be viewed, downloaded, and printed without extracting a ZIP.

v1.0.0

June 15, 2025

Initial deployment. Detailed release notes weren’t tracked yet at this point — this entry marks the starting point of the system’s history.