32 Commits
Author SHA1 Message Date
jason5ng32andClaude Fable 5.1 4c9ab04bc5 Improvements
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-22 11:37:55 +08:00
jason5ng32 b616b7e00e Improvements 2026-10-06 10:48:50 +08:00
jason5ng32andClaude Opus 5.5 02476aec7e Fix(datasets): abort on a lost lock, roll back new files, validate watched files
- A compromised lock (failed heartbeat; another process may take it over)
  aborts the update holding it, download included, before it publishes,
  instead of only being logged.
- A failed publish removes files it had just added when the dataset had none
  before, as well as restoring replaced ones from backup: never half a set.
- The watcher reloads hand-placed or externally published files only once
  they have held still for the settle interval and passed the row's
  validator, so a file still being copied never replaces healthy data.
- Back to write-file-atomic 8, with engines aligned to its range
  (^24.15.0 || >=26.0.0).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:31:28 +08:00
jason5ng32andClaude Opus 5.5 65e8432768 Fix(datasets): bound scheduled runs, catch up by schedule, retry failed reloads
- Scheduled and catch-up runs give each dataset 30 minutes, download
  included, so a stalled upstream can't hold up the rest or, under croner's
  overrun protection, every later run.
- Downloads go through fetchUpstream (project User-Agent, a timeout on the
  wait for a response); the body stays bounded by the caller's signal.
- Catch-up asks whether a scheduled run fell between the last check and now,
  instead of a 25-hour age that missed a check skipped just after one.
- A remote that names no version (no Last-Modified) is always fetched, not
  taken for unchanged; maxmindIdentifier is null when an edition omits it.
- The watcher records a dataset's files as loaded only after the reload
  succeeds, so a failed load is retried on their next change.
- write-file-atomic 7, which supports the project's Node >=24 (8 needs 24.15).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:29:48 +08:00
jason5ng32andClaude Opus 5.5 287c50fe1a Feat(datasets): one updater engine for every offline dataset
MaxMind and CAIDA each had their own updater with its own lock, state file,
publish step, boot download and timer. One engine (common/dataset-updater.js)
now owns all of that, and each dataset is a row in common/datasets.js that
holds only its own logic: find the newest remote version, fetch or distill
it, validate, reload.

- Locks: proper-lockfile, a heartbeat lock shared by every process, so a
  crashed holder's lock goes stale on its own; a booting server waits for a
  live holder and loads what it published.
- State: one file per dataset, written with write-file-atomic; the old
  .maxmind-update-state.json / .caida-update-state.json are read once, so an
  upgrade doesn't re-download.
- Schedule: croner at a fixed time (DATASET_UPDATE_CRON, default 04:30 local)
  instead of 24 h from process start, plus a catch-up run at boot for a check
  a downtime skipped. DATASET_AUTO_UPDATE (default on) replaces
  MAXMIND_AUTO_UPDATE / CAIDA_AUTO_UPDATE, which still apply when it is unset.
- Reload: the engine watches every dataset's files, so a publish by another
  process or a hand-placed .mmdb is picked up; MaxMind's own watcher goes.

MaxMind's City and ASN editions now publish together under one version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 02:21:58 +08:00
jason5ng32 45be0e711c Improvements 2026-09-17 10:33:38 +08:00
jason5ng32andGPT-6 cc0053ac16 Refactor(ipinfo): share Intl country names with the frontend
Replace country-code-lookup with the existing Intl.DisplayNames helper.

Co-Authored-By: GPT-6 <noreply@openai.com>
2026-09-16 18:29:19 +08:00
jason5ng32 a1ee760d88 Improvements 2026-09-13 22:47:01 +08:00
jason5ng32 c596a15b30 Improvements 2026-09-05 21:14:57 +08:00
jason5ng32 65145ca5e8 Improvements 2026-09-03 19:10:40 +08:00
jason5ng32andClaude Opus 5 a13e89fbae Chore(ui): add the shadcn-vue button-group primitive
Needed to join the DNS resolver's record-type Select and its input into
one bordered control. Added through the shadcn-vue CLI, so the files stay
verbatim upstream output and can be re-synced.

The same run resynced ui/separator, which still carried a hand-written
early-refactor version, to current upstream — data-slot and
data-[orientation] variants instead of a JS ternary, behaviourally the
same for all three existing callers. It also refreshed one transitive
postcss patch in the lockfile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 18:07:05 +08:00
jason5ng32 89cbb35401 Chore(deps): bump @cloudflare/speedtest and vite 2026-08-23 12:00:10 +08:00
jason5ng32 2eddb7287e Improvements 2026-08-20 11:35:55 +08:00
jason5ng32 a703442ba8 Improvements 2026-08-20 11:34:47 +08:00
jason5ng32andClaude Fable 5 0ff2615bab Chore: Add vue-input-otp dependency
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 10:11:57 +08:00
jason5ng32 6e18ba2ded Improvements 2026-08-13 16:27:48 +08:00
jason5ng32andClaude Opus 5 83bdd54674 Feat(ip-infos): show each IP's time zone, derived from its own coordinates
Every geo route now returns an IANA zone name, added on the way out by the
withTimeZone() middleware rather than by any handler — including the private
API pass-through, which needs no new upstream field.

The zone is derived from the latitude/longitude the response itself carries,
not from a second lookup of the same IP. Asking another database would
eventually place the address elsewhere and print a zone that contradicts the
city next to it. Coordinate accuracy measured against MaxMind's own time_zone
over 30k sampled addresses: 99.80% exact, 99.98% correct offset; the rest are
neighbouring zones across a land border that share an offset anyway.

Only the name ships. Geo routes sit behind a 24h edge cache, so a cached UTC
offset would be an hour wrong for everyone between a DST switch and the
entry's expiry — the frontend renders the offset per view instead, reusing
the formatter the analytics path already had.

The field travels the full report path: event payload, builder, schema (as a
typed timeZone kind, not a free string), the /r/:id table, the Markdown
export and the JSON download.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 17:44:18 +08:00
jason5ng32 f1728105ab Change word map dependent 2026-08-09 18:56:29 +08:00
jason5ng32andClaude Fable 5 ae0eaee838 Chore(deps): bump dependencies
Routine minor/patch bumps (vue 3.5.41, vite 8.2, sentry 10.69, firebase
12.17.1, …) plus @tanstack/vue-table 8 → 9; pnpm 11.20. Vue 3.5.41
packages allow-listed in the workspace minimumReleaseAge exclusions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 12:15:37 +08:00
jason5ng32 007d81cb98 Improvements 2026-07-25 10:11:10 +08:00
jason5ng32andClaude Fable 5 ab7cc4a391 Chore: bump version to 7.2.0 and fill changelog in all locales
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 23:07:22 +08:00
jason5ng32 1aca785105 Improvements 2026-07-16 00:26:25 +08:00
jason5ng32andClaude Fable 5 9bfbf21a60 Feat(monitoring): integrate Sentry across frontend and backend, env-gated end to end
Frontend (@sentry/vue, loaded via build-time-gated dynamic import — no
VITE_SENTRY_DSN_FRONTEND, no Sentry in the bundle; init joins the mount
gate so instrumentation precedes the first render):
- uncaught errors, route-change tracing / Web Vitals, error-only masked
  Session Replay, console.error capture fingerprinted per message prefix
  so each fallback source stays a distinct issue
- explicit ip-source:exhausted signal via the app-events bus
- hidden source maps uploaded (then deleted) by @sentry/vite-plugin,
  gated on SENTRY_AUTH_TOKEN

Backend (@sentry/node, bootstrapped by sentry-instrument.js via
node --import so ESM loader hooks land before express; never loaded
without SENTRY_DSN_BACKEND):
- Express error capture + per-route tracing
- logger hook mirrors warn+ lines to Sentry Logs and elevates error+
  to grouped, alertable Issues
- Sentry Crons check-ins for the MaxMind / CAIDA / service-status jobs
  via the no-op-without-DSN wrapper common/sentry-cron.js

Delivery & ops:
- first-party tunnel /api/monitoring relays envelopes past ad blockers;
  DSN-allowlisted (never an open relay), exempt from the shared rate
  limiter with its own generous ceiling
- rate-limit ledger file becomes opt-in now that the warn line reaches
  Sentry Logs; ecosystem.config.cjs carries the --import flag for pm2

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 11:16:17 +08:00
jason5ng32 7f46d6cfdd Improvements 2026-07-08 12:46:03 +08:00
jason5ng32 38a0db748f Update Packages 2026-07-08 11:54:42 +08:00
jason5ng32 53265e0bcd Improvements 2026-06-30 10:48:33 +08:00
jason5ng32 d72cff6449 Improvements 2026-06-28 12:02:39 +08:00
jason5ng32 691924aa9c Improvements 2026-06-24 09:06:00 +08:00
jason5ng32andClaude Opus 4.8 976e971371 Feat(nav): surface Advanced Tools sub-tools in the navigation
Turn the nav's "Advanced Tools" item into a discoverable menu of its
sub-tools instead of a bare scroll link:

- Desktop: a NavigationMenu (shadcn-vue) opens on hover; clicking the
  label keeps the original behavior of scrolling to the section.
- Mobile drawer: an inline Collapsible, expanded by default, so phone
  users see the bottom-of-page tools when they open the nav.
- Picking a tool scrolls to the Advanced Tools section, then opens that
  tool's in-page drawer (no jump to a standalone page). The list mirrors
  Advanced.vue's original-site gating so unavailable tools aren't shown.

Also swap the Preferences icon to Cog (nav button + panel header), and
add the navigation-menu primitive via shadcn-vue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 10:52:50 +08:00
jason5ng32 4fe3cd0948 Improvements 2026-06-14 18:55:44 +08:00
jason5ng32 18fa8cef2b Improvements 2026-06-06 12:02:38 +08:00
jason5ng32 1d09e2b60f Improvements 2026-05-31 10:34:01 +08:00