gora.
Aperwind — Thailand property catalogue

Admin panel

What the person at the screen sees: catalogue storefront, sync approval, enquiries, sessions and replays.

~6 min read · 1110 words

TL;DR

The admin panel lives at /admin and has two halves. The first runs the storefront: which listings to show, in what order, which projects to push forward, and approval of the morning transfer from the supplier's sheet. The second is in-house analytics: sessions, funnel, enquiries, search demand, errors, speed and session replay.

It is a separate branch of the application with its own root layout, its own palette and system fonts. It is password-protected, marked noindex, never served from cache, and loads no external resource at all.

Admin panel tour

Catalogue: what to show

The "Catalogue" section answers two different questions in one place — how the storefront shows listings, and which listings it shows.

There are four storefront settings: cards per page (6 to 96), default order, what to do with listings that have no photograph (push them down or remove them entirely), and how strongly the cover tier overrides everything else. They are shared by the catalogue, the project hub and the selections.

Removing a listing from the storefront is the main operation here, and it does not mean deletion. A removed card disappears from lists, from the home page, from selections, from guides, from the "similar" block and from every public counter. Its own page stays: status 200, the same robots, a link in the sitemap and a link from its project page. The operator sees that distinction in a table on the page, because the two get confused constantly.

The "what will change" preview

Before settings are saved, the panel shows five lists of consequences, computed by the same code the site itself uses:

Will leave search. Pages moving from index to noindex: a district listing with two properties instead of three.

Will disappear entirely. URLs that become 404. Selections and listings have different overflow policies — one returns 404, the other clamps the page number — so there are two lists, not one.

Projects will leave the lists. Their pages and their sitemap entries stay alive.

The point of the preview is simple: a setting like "remove listings without photographs" looks cosmetic and can in fact pull a dozen listing pages out of the index.

Card order and recommended projects

The operator pins cards by hand, but what is stored is only the pinned head of the list; everything below keeps its computed order. A pin is either global or scoped to one result set, and the scoped one wins on its own page. If the visitor chose "cheapest first" themselves, pins drop to the end of the comparison: the chosen sort must mean exactly what it says.

A separate tab holds recommended projects. The owner marks the developments that come first on the home page and under the "recommended" sort. This changes order only: no object appears or disappears, counters and the sitemap do not move. A bulk button works on the rows currently found — type a developer's name into the search, mark all nineteen projects at once.

Pins that stopped working are listed separately with a reason: the listing left the sheet, was removed from the storefront, is not part of this result set, the slug moved. They are never re-snapshotted automatically — a refreshed snapshot would quietly revive a pin on a different object.

Approving the morning sync

The /admin/catalog/sync page shows the result of the nightly transfer: what changed, before-and-after fields, quarantine, errors, and any divergence between the database generation and the cache generation. A plan is approved or rejected as a whole, by its exact id.

The pipeline stops and asks for a human whenever a change looks too large: more than 25% of listings changed, more than 5% of rows missing, a single sheet shrinking by more than a quarter, a single price jumping by more than 30%. A new project never appears automatically — only through an explicitly reviewed mapping, and structural errors in the source cannot be waved through with the approval button. If the service fails or needs review, a bot sends a direct message with the status, the alarm names and a link to this page.

Analytics

The remaining sections are about visitors. Overview, objects (including pages nobody opens), funnel, search and demand, sessions with a timeline, enquiries with status changes, errors and rage clicks, speed at p75, and a live feed polling every five seconds that stops on a hidden tab.

The dimensions — city, district, property type, specific object — are derived from the URL by the same functions that render the catalogue, so a figure in a report and a page on the site do not diverge. The period is chosen with buttons (1, 7, 30, 90 days) or as a custom range capped at 400 days, so that one click cannot start a multi-year scan.

Session replay

A session can be watched as a recording. rrweb starts from the second page of a visit — single-page arrivals and bots are not recorded. Input fields are fully masked, and the contact block and enquiry form are not recorded at all. Recordings live 7 days, events 180, enquiries 3 years. Playback runs in a sandbox with scripts disabled: a recorded page cannot execute anything inside the admin panel.

Enquiries and privacy

An enquiry is a separate business record. It deliberately has no foreign key to the session: clearing statistics must not delete enquiries, and an enquiry must survive with collection switched off and cookies refused.

A visitor turns collection off on the "Data and privacy" page. The opt-out is end-to-end: the cookie is not issued, the collector does not start, replay does not start, the intake routes reject the requests. Our own people get a link that switches collection off on open; it can only switch it off, switching on is always a button. For erasure on request there are three commands: forget a visitor, forget a session, anonymise an enquiry.

Signing in

The password and the signing key are both required and at least 16 characters; with empty or short values the door is closed to everyone rather than open. The cookie's lifetime sits inside the signature and is checked on the server, because the Max-Age attribute can be edited by anyone. The access check is called in every protected handler: checking it in the layout covers pages but not the API.

One known rough edge is visible in this section: 22 inline styles remain in 11 admin files. In production the security policy strips them silently; locally they apply, so the difference is invisible by eye. The "Catalogue" section has no inline styles — only classes.