Twelve views, no framework, no build step. The behaviour that matters is the same on every one of them: nothing blocks, a failed card takes only itself down, and every number says what population it covers.
No view issues a Solr query while PHP renders the page. Headings, captions, table headers and empty states are emitted server-side and nothing else; every number arrives afterwards over a separate request. The rule is absolute.
Figure 1 — the async contract. Retrying re-runs one request, not the page, and a slow query names what it is doing in words rather than showing a bare spinner.
Every card carries the same three-part loading state, emitted server-side so it is in the page from the first byte: a flat indeterminate bar, a worded label saying what the card is doing, and an elapsed-seconds counter that appears after five seconds. At twenty seconds the label says it is still working.
| Limit | Value | Why |
|---|---|---|
| Server-side query timeout | 20s, clamped 3–45 | Configurable, but not beyond a request’s life |
| Browser request timeout | 50s | Deliberately longer, so a slow query reports its own diagnosis rather than being cut off with a generic message |
Errors are diagnoses, not stack traces. A timeout, an unresolvable hostname, a refused connection, rejected credentials and a missing core are five different problems with five different answers, and “Solr query failed” tells them apart for nobody.
The panel draws on two completely separate sources, and keeping them straight is the most load-bearing fact about it.
| The web plane | The search plane | |
|---|---|---|
| What it is | Your web server’s access logs, parsed and indexed by Loghound | The request log of the Opensolr search indexes your account owns |
| Where it lives | Loghound’s own two indexes | Opensolr’s analytics, on the platform |
| Views | Overview, Bot forensics, Fingerprints, Networks, Sessions, Performance, Virtual hosts | Index analytics, Query analysis, Who is querying, Storage & bandwidth |
Not over SSH, not by shell, not by reading a log file off a node, not by opening a connection to a cluster host. The only Solr it ever speaks to is the pair of indexes it provisioned for itself. Everything it knows about your search indexes it learned by asking the Opensolr platform, which already holds that data and already enforces ownership on it. There is no agent to install, no log format to detect, no volume problem, and no way for Loghound to be blamed for something happening on a customer’s node.
There is no generic Solr passthrough on either plane. Every query is built server-side from constants and allowlists; the browser sends a view name, a range token, allowlisted facet selections and free search text — nothing that is query syntax.
Some operations exceed what a request can hold open, and an action that times out leaves an operator with a dead tab and no idea whether it ran. So a job is a list of named steps, and each poll executes exactly one step and returns. Every step is bounded far below the execution limit, so no single request can time out however long the whole job takes.
There is no background process and no worker daemon, because the product has to install on a bare box with no toolchain. State lives in a small SQLite file rather than in a process, which is what makes a job survive a refresh.
- Idempotent start. A double-clicked button, a refreshed tab and a second window converge on one job.
- Honest progress. The steps are known in advance, so the percentage is a real fraction rather than a guess.
- Cancellable at boundaries only. Cancel is honoured before a step starts and never during one, because a half-executed step is the one state nobody can reason about.
- Reattach. A reloaded page picks up what it was watching without the browser having had to remember an id.
- Nothing in a job deletes anything. The panel never issues a delete; the retention command does.
Demo mode is opt-in and never inferred, and every page carries a banner saying the numbers are fabricated. A Solr that is merely unreachable produces an error inside the card that failed and an empty state — never fake data.
php tools/panel-preview.php # writes a throwaway config, prints a password php -S 127.0.0.1:8099 -t public # or point any vhost at public/
The preview script refuses to overwrite an existing configuration, because that file holds the API key and the beacon signing secret. The synthetic world it generates deliberately contains a rotating-proxy fleet, a headless cluster on rented boxes, declared crawlers including the AI ones, scripted clients, and human sessions whose four timing numbers differ the way real ones do.
- The panel needs JavaScript for its numbers. The page, its navigation, the range picker, the session search form and every settings form work without it — they are server-rendered and post normally — but the cards fill in over a separate request and stay in their loading state if scripting is off. That is the price of never blocking first paint on Solr.
- No per-card cache. Changing the range or a filter is a full page navigation and every card refetches.
- Job state is per session. Signing in as a different user makes a running job invisible rather than transferring it, which is the fail-closed choice.
- The distinct-address count is approximate above about a hundred. The panel says so wherever it shows one.
- The map is country resolution, and the page says so rather than implying a precision it does not have.
- Daily rollup documents are currently counted as sessions in the session totals, by up to one document per day in the selected range. It is a known bug in the panel’s query construction, recorded as such in the repository rather than presented as a trade-off.