Tech Stack
TrueNAS SCALE (Kubernetes, NodePort), Docker (Apache), Deno (Supabase Edge Functions), Supabase (Postgres + Realtime), n8n (webhooks), Vite + React (JS), Tailwind CSS, TanStack Query, Tailscale, Netlify (read-only client)
Overview
Field engineers used to track bad UPS batteries in Word, then blast email chains to coordinate replacements. I replaced that manual flow with a centralized web app that parses UPS report summaries, lists open items in a sortable table, and moves completed work to history—with lightweight, path-based hosting and secure access for the whole team.
Solution
1. Server & Infrastructure Setup
Hosted on TrueNAS SCALE, exposed via NodePort and served through an Apache (Docker) container alongside other internal apps.
Path-based routing under
/openitemskeeps deployment simple and avoids extra subdomains/certs.Production build artifacts are static, making the site extremely stable and low-maintenance.
2. Kubernetes & Docker Orchestration
Containerized frontend is deployed as a simple pod; Apache handles static assets and path routing.
TrueNAS Apps + NodePort provide a clean interface to expose the service internally without external ingress complexity.
3. Secure Remote Access
Company-wide access through Tailscale; employees on the tailnet reach the app reliably from any location.
Access scoping is controlled by tailnet ACLs rather than app-level auth (keeps v1 friction-free).
4. Backend & Data Flow
Supabase Postgres stores
ups_items(parent) andbad_batteries(children).Deno (Supabase Edge Function)
/ingest-report:Parses pasted report text (UPS Identifier, test date, cabinets/jars, building, tech, and per-battery details).
Normalizes units to mΩ and captures a severity label (e.g., “Moderately High”).
Creates a content fingerprint (SHA-256) combining UPS ID + date + normalized report to prevent exact duplicates.
Inserts parent + child rows in a single transaction and then fires a webhook to n8n (best-effort) to generate a ticket.
Enrichment loop: frontend polls the row for a
ticket_numberfor up to ~45 seconds; Supabase Realtime also invalidates the open-items query whenticket_numberupdates.
5. Frontend UX (Vite + React + Tailwind)
Open Items table with sorting (Ticket #, UPS ID, Highest Severity, Date, # Bad, Status).
Accordion details show strings/jars with resistance and severity.
Add Open Item modal accepts raw report text; on submit:
Shows a full-screen Saving… overlay while the edge function parses & inserts.
Immediately refreshes the table and background-polls for the new
ticket_number.
Inline status editing (Must Schedule / Scheduled / In Progress / Completed) with Save/Cancel batching.
Mark Completed stamps a
completion_timestampand moves the item to Closed Items (kept for history).Typography, spacing, and interactions were tuned for fast triage by operations.
6. Read-Only Client
A second client build is deployed on Netlify for view-only access—no Add or Edit controls—so stakeholders can check live status without risking data changes.
Results
Single source of truth for open UPS items—no more scattered Word docs or email threads.
Consistent parsing eliminates hand-entry errors and standardizes severity & resistance units.
Faster handoffs: ticket numbers appear automatically post-ingest via the n8n webhook loop.
Low-ops hosting: static frontend under
/openitems, one edge function, and Supabase as a managed datastore.
Key Takeaways
Start simple: static client + one edge function + managed Postgres goes a long way.
Path-based routing under an existing Apache container keeps infra lean and predictable.
Realtime invalidation + short polling makes async enrichment feel instant to users.
A separate read-only build is an easy way to widen access without adding auth in v1.