Frontend case study · 2020 – 2023 · Australia

DGtek Provisioning Portal

Sole frontend engineer — fibre network operations platform

A fibre network operator needed one system where its own staff, its reseller ISPs and the public could all answer the same question — can this street address be connected, and what happens next? I owned the entire frontend: three applications, a Web Worker data layer that kept a very heavy admin interface responsive, and seven npm packages — the ones that answer that question ended up embedded on the resellers' own websites.

14repositories
~72klines of frontend
7published packages
3audiences
v8.0admin dashboard

The brief

Selling fibre is mostly a data problem

DGtek builds and lights fibre — from a Melbourne base outward across Australia — then wholesales it to retail service providers who sell to households and businesses. Before anyone can be sold anything, three facts have to line up: is the building already lit, is it inside a serviceable polygon, and if not, what would it cost to dig from the nearest pit to the basement.

All of that lived in spreadsheets, KMZ exports and email threads. The portal replaced it with one platform on three surfaces — with the awkward constraint that the reseller and the network operator see the same customer record but must never see the same fields.

DGtek staff

Admin dashboard

  • Network footprint: buildings, polygons and pits edited on the map
  • Customers, services, SLAs and per-partner discounts
  • Ticketing and installation scheduling by week
  • Reporting: revenue, churn, ARPU, connection pipeline
  • Roles, permissions and editable system messages

v8.0.041,672 LOC

Reseller ISPs

Partner cabinet

  • Address serviceability check with ETA and description
  • Customer onboarding and service lifecycle
  • Suspend, resume and cancel requests routed to DGtek
  • Installation slot booking
  • Tickets, documents and company profile

v7.1.415,351 LOC

Public & new partners

Entry & registration

  • Partner onboarding with ABN, email and phone validation
  • Sign-in and password recovery
  • Public build-cost calculator
  • Document library

v4.0.03,340 LOC

Architecture

The UI thread never opens a socket or a database

The admin dashboard holds thousands of buildings and customers, and every one of its tables filters, sorts and exports. Doing that on the main thread meant dropped frames on every keystroke, so I moved the whole data layer into a Web Worker and left the UI with nothing but messages.

The worker owns the API client, the session credentials and a versioned IndexedDB cache of ten object stores with secondary indexes. Components dispatch { route, action } and receive a result envelope; a separate MessageChannel port carries bulk table rows so large payloads never queue behind small ones. Credentials are kept encrypted rather than sitting in plain localStorage — obfuscation against a casual look at devtools, not a security boundary, since the key ships in the bundle. The real boundary is server-side.

The shape has a name now — local-first, sync engines, the architecture behind Linear and Replicache. In 2020 it was just the only way I could make a table of several thousand buildings filter without dropping frames, and it turned out to be the decision the codebase was built on for the next three years.

Main thread Vue 2 + Vuetify tables · charts · map no fetch no storage access Web Worker message controller route → action action modules customers · buildings reports · import credentials AES · crypto-js IndexedDB 10 object stores secondary indexes REST API buildings · customers services · tickets postMessage { route, action } { status, result } channel port bulk table rows read / write cursor scan → report rows signed request delta refresh
Every read and write crosses one postMessage boundary. The teal path is the one that removed work from the backend: monthly revenue, churn and per-building reports are computed in the worker by walking IndexedDB cursors over the cached customers and services, so the charts have no report endpoint behind them.

Geospatial

Answering “can you connect this address?” four different ways

A serviceability check looks like one lookup and is really a cascade. Some buildings are already lit and matched by address. Some sit inside a polygon that is under construction. Some addresses Google will not geocode at all — a common outcome for newly built apartment towers, where the unit number exists in the listing but not yet in the map data.

I built the chain so that a failure at any stage falls through to the next one, and the last stage is a human: an address nobody can resolve becomes a request in the operations queue rather than a dead end for the partner trying to sell a connection.

Nothing in the model is pinned to a city. Polygons, pits and building records are just data behind the same endpoints, and every status carries its own editable label and ETA — so the network grows into a new state by loading more rows, not by changing code.

Address typed Geocode Google Maps + Geoscape Marker match lit · footprint buildings Point in polygon 3 serviceability layers DGtek address store partial string match over cached records Status + ETA + description Not serviceable Record returned Manual review → ops queue lat / lng no coordinates returned match found inside polygon outside every polygon match nothing found
The fallback chain behind a single search box. Each stage that misses hands the address to the next; only the last one involves a person. Partners see the same wording for a given status everywhere, because the status text and its ETA are editable settings rather than strings in the code.
LIT — on-net today
Footprint — service available
Under construction
Coming soon
Not available

Selected problems

Four things that were harder than they looked

Reports that outgrew the report endpoint

Finance wanted monthly recurring revenue, churn and ARPU broken down by building and by partner, recomputed as soon as anyone changed a service. Fetching that from the server meant a slow endpoint that went stale the moment it returned.

Because the worker already held customers, services and partners in IndexedDB, I moved the calculation there: cursors over the buildingId and resellerId indexes produce the report rows, discounts are applied across their own validity window, and Google Charts renders straight from the local result.

Importing customers from whatever the partner sent

Partners migrated their customer bases in spreadsheets of wildly varying quality. A single bad row could not be allowed to reject the file, and a silently accepted bad row was worse.

The import pipeline sorts every problem into three buckets — fatal errors that stop the file, errors that block a row, and warnings that let it through flagged — and shows the operator the parsed result as an editable table before anything is written.

Quoting a fibre haul before anyone digs

Sales needed a number for “what would it cost to reach this building?” on the call, not after an engineering review.

The calculator takes the distance from the building to the ten nearest pits and prices the route: metres through tunnels, through Telstra or NBN ducts, open trench and underboring, pit types P5 / P6 / P8–P9, breakouts, asphalt, concrete or bluestone reinstatement, and traffic management per day plus the plan itself. It exports a client-ready PDF quote.

The table the field engineers actually wanted

Network engineers were keeping PPPoE credentials, IP allocations and VLAN IDs in a side spreadsheet because the portal only showed them read-only.

I gave them one screen of every customer with login, password, IP, router subnet, DGtek VLAN and RSP VLAN editable in place, alongside speed in and out — and exports to Excel, CSV and KML for the tools that live outside the portal.

Reuse

The packages left the building

The map, the address inputs and the calculator were each needed by more than one application, and the applications shipped on different schedules. Rather than a monorepo I published them to npm and pulled them in at build time, so each app upgraded when it was ready to.

Then they stopped being internal. DGtek handed the coverage-check packages to its reseller ISPs to embed on their own sites, which turned a component into a distribution channel: a reseller's page draws DGtek's live coverage polygons, resolves a visitor's address against them, and — this is the part that made it a product rather than a widget — reads that zone's settings back from DGtek's admin to quote a realistic connection time, two days or two weeks depending on where the address falls, before handing the visitor to a connection request.

Coverage is edited in exactly one place. Everything downstream — the portal, DGtek's own corporate site, every landing page, and a reseller's site DGtek does not control — follows from the same source, because none of them own a copy of the data.

Package Version What it does
dgtek-portal-map-package 1.4.8 Map rendering, serviceability polygons, building CRUD, and the worker + IndexedDB layer the host app talks to
dgtek-portal-calculator 1.2.9 Build-cost estimate from building to nearest pit, with PDF quote export
dgtek-portal-polygons-editor 1.1.5 Drawing and editing serviceability polygons on the map, emitting save events to the host
dgtek-portal-pits-editor 1.0.95 Placing and editing pits, the anchor points every haul distance is measured from
portal-google-autocomplete 1.0.85 Address autocomplete as a custom element, with server-error events the host can handle
simple-google-autocomplete 1.0.1 The lightweight variant for public pages — address, components, coordinates, nothing else
garevna-date-functions 1.1.5 Week arithmetic behind the installation scheduler, accepting and returning any date format

Stack

What it was built with

Core

Vue 2Vuetify 2Vue RouterVuexWebpackSCSS

Platform

Web WorkersMessageChannelIndexedDBService Worker / PWAcrypto-js

Domain

Google Maps APIGeoscapeGoogle Chartsxlsxjson2csvtokmlPDF export

Delivery

npm publishingDockerHelmExpress static serverESLint standard

Where it stood, honestly

This is a Vue 2 codebase from the Vue 2 era and it shows its age. Shared state reaches components through a global symbol registry rather than injection, because the worker sits outside the component tree and Vue 2 had no composition API to hand. Publishing the shared packages to npm bought independent release cycles but left build scripts that install dependencies at build time — workspaces in a monorepo, today. And with one engineer against a moving spec, the coverage went to the parts a person could check by looking; the report calculations and the import validation are pure functions sitting in isolation, and they are exactly what I would put under test first.

What did hold up was the decision to put the data layer behind a message boundary. It survived eight major versions of the admin dashboard, and the UI on top of it could have been rewritten in any framework without touching it.

Availability

Working together

Expected compensation

from $3,500 / month