Commit Graph

7 Commits

Author SHA1 Message Date
631385721f Next healthcheck due date, plus a test suite (hc-nkq)
Lets a technician record when a site should next be looked at, and surfaces
that in the dashboard so it can be used for planning.

Server:
- migration 0002 adds healthchecks.next_due, a VARCHAR(10) 'YYYY-MM-DD'
  calendar date rather than a timestamp — it's a diary date with no
  time-of-day, and a timestamp would render as the wrong day off-server.
- Kept per-session rather than on a sites table, so the history of what was
  scheduled when is preserved. A site's current next-due is the value on its
  most recent session: if the latest visit scheduled nothing, the site reads
  as unscheduled rather than showing the just-completed visit as overdue.
- Validate::optionalDate() round-trips through createFromFormat, which
  rejects both '2026-2-3' and '2026-02-30' (silently rolled to March 2nd).
- next_due follows the same missing-vs-null PUT contract as finished_at, and
  lives in its own Store::setNextDue() so finishing or reopening a session
  never disturbs the date and vice versa.
- Dashboard shows it on the site list, the per-site session table and the
  session detail, flagged overdue / today / soon (within a fortnight).

Plugin:
- Date control on both the active and finished panels — the moment you know
  when to return is often wrap-up, after the report is generated.
- Write-through like every other mutation. Bad input is rejected with a
  notice rather than silently clearing an existing date.
- Included in both the Markdown and HTML reports.

Tests (new — tests/, export-ignored from the plugin zip):
- Hand-rolled harness, no composer/PHPUnit, in keeping with the repo.
- 42 tests: plugin-side date parsing units, plus server integration tests
  driven over real HTTP against a temp `php -S` instance with a throwaway
  SQLite DB, so a real server/config.php is never touched.
- Mutation-checked: dropping the array_key_exists guard on PUT fails three
  tests, as intended.
2026-07-23 12:02:39 +01:00
d4f643528d Dashboard: reopen a finished healthcheck (hc-lh6)
Adds a Reopen button on the session-detail view for finished sessions.
POSTs to /dashboard/healthchecks/{id}/reopen with a per-session CSRF
token; on success clears finished_at and redirects back to the session.

Also tightens Controllers/Healthchecks::update so a PUT with no
finished_at key is a no-op instead of silently setting it to NULL —
otherwise an empty PUT would reopen any completed healthcheck.

After reopening, the WP-side Session::resume($id) flow pulls the now-
active session back into an editable local session on the client site.
2026-07-21 11:52:00 +01:00
8dc31c53f2 Dashboard: add session step-detail drill-down (?site=X&hc=Y)
Session list rows are now clickable links. Clicking a session shows all
its steps with status badge, notes, and last-updated time. Step IDs are
formatted into human-readable labels. Status badges cover all five states
(done, skipped, blocked, n/a, not started).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-10 09:17:07 +01:00
9f13d871bf Server: HTML dashboard at /dashboard showing all sites + session history
Browsable at /dashboard — authenticates via ?key=<api_key> once per browser
session (stored in a PHP session cookie). Lists all sites ordered by most-
recently-active, with session counts and in-progress badge. Click any site
to see its full healthcheck history with dates and durations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-10 09:01:05 +01:00
3c6220e64b Server: MySQL support (portable schema + driver-aware upsert)
Some VPS configs can't get pdo_sqlite at all — Ubuntu 20.04 with Ondrej
Sury's PHP 8.3 builds doesn't ship php8.3-sqlite3, and OS upgrade isn't
always an option. Make MySQL the documented default while keeping SQLite
working where it's available.

- Schema (0001_initial.sql): TEXT → VARCHAR(N), INTEGER → BIGINT, keys
  declared with explicit PRIMARY KEY (...) syntax. Drops the previously
  unused request_log table (it had AUTO_INCREMENT, which spells
  differently on each engine and nothing wrote to it anyway). Both
  engines accept the new column types and indexes are IF NOT EXISTS for
  retry-safety.
- Migrations.php: guard commit()/rollBack() with inTransaction(). MySQL
  implicitly commits any open transaction the moment it sees a DDL
  statement, so by the time we explicitly commit() the transaction is
  already gone and PDO throws "There is no active transaction". Same
  schema in PHP CREATE TABLE migrations also moved to VARCHAR/BIGINT.
- Store::upsertStep: driver-detect via PDO::ATTR_DRIVER_NAME and emit
  ON DUPLICATE KEY UPDATE for MySQL, ON CONFLICT (...) DO UPDATE for
  SQLite/PostgreSQL. VALUES(col) (vs new.col aliasing) for MySQL 5.7
  compatibility.
- Db.php: when DSN is mysql:, SET NAMES utf8mb4 + sql_mode strict on
  every session so we get sane behaviour regardless of server defaults.
  SQLite branch (PRAGMA foreign_keys/journal_mode/synchronous) unchanged.
- config.php.example: MySQL DSN is now the default + an inline SQLite
  alternative block.
- DEPLOY.md: new "Database — MySQL or SQLite" section explaining when to
  pick which and showing the CREATE DATABASE / CREATE USER / GRANT
  statements. Install snippet split so SQLite-only steps (mkdir data,
  chmod 770) are clearly optional.

Verified end-to-end on a live MySQL 8.0.34 box: POST creates session
(201), PUT step inserts (200) and updates via the upsert branch (200),
GET returns the round-tripped state, /sites lists distinct site_keys.
SQLite path still re-applies the migration idempotently locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-29 15:36:45 +01:00
f6d2573a35 DEPLOY.md: version-agnostic FPM socket + PHP 8.1+ requirement louder + sites-enabled backup gotcha
Two issues hit during a real deploy:

1. The nginx example pinned fastcgi_pass to /run/php/php8.2-fpm.sock.
   When the target box runs a different PHP-FPM (8.3 here), the socket
   doesn't exist and nginx 502s. Switch to /run/php/php-fpm.sock (the
   alternatives symlink installed by Ondrej Sury's packaging) so the
   default config works against whatever current PHP is installed,
   with a comment explaining the fallback for boxes without the symlink.

2. The PHP version requirement was buried in a one-liner. Promote it to
   a multi-line note that names the specific 8.x features used, says
   loudly that 7.x will not parse the code, and points at Sury's repo
   for boxes stuck on an old default.

Also added a callout warning against cp foo.conf foo.conf.bak inside
sites-enabled/, since nginx loads every file in that directory and the
backup becomes a second vhost claiming the same server_name (silently
shadowed via the "conflicting server name … ignored" warning).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-29 13:41:54 +01:00
1782e52504 Central history server + plugin write-through sync (epic hc-0p1)
Adds a PHP/SQLite history server in server/ and refactors the plugin to
write every session change through it. Healthcheck history now survives
plugin uninstall and groups across dev + live URLs for the same engagement
via an editable site_key (defaults to the normalised host).

Server (server/):
- Front controller + hand-rolled autoloader, no framework, no composer
- SQLite default DSN; swap to MySQL by changing config.php
- Schema: healthchecks (PK id, UNIQUE (site_key, started_at)) + step_updates
  (PK (healthcheck_id, step_id)) + request_log; auto-migration runner
- 8 endpoints: POST/GET/PUT healthchecks, PUT/GET step rows, GET step history
  with exclude_id, GET /sites (recent), GET /step-counts (badge data)
- Bearer auth via hash_equals; HTTPS expected (plugin enforces client-side)
- DEPLOY.md with Apache/nginx vhosts, Let's Encrypt, SQLite backup cron,
  and the /home/www/ perm gotcha
- dev-router.php works around PHP -S 405-ing dotted uniqid paths

Plugin:
- ATT_HC_Api HTTP client reads ATT_HC_API_URL/ATT_HC_API_KEY constants
  from wp-config.php; refuses non-HTTPS with a loopback dev exception
- ATT_HC_Session is now write-through: every start/update_step/finish/
  set_autocheck POSTs or PUTs to the server first, then updates the local
  WP option cache. No drift possible — failures throw ATT_HC_Api_Exception
- previous() now reads from /healthchecks?include=steps and reconstructs;
  the old att_hc_previous_session local option is gone
- ATT_HC_Session::resume(id) hydrates a server session into the local cache
- Start screen: editable site_key (defaults to normalise_site_url()),
  datalist of recent engagements, table of in-progress sessions for the
  chosen key with Resume buttons. Double-click guard on start + resume
  handlers short-circuits if a session is already active
- Per-step <details> disclosure shows "Previous notes (N)" badge from
  /step-counts; lazy-loads detail rows on first expand via admin-ajax,
  caches via data-loaded, resets on error so user can retry
- All admin handlers catch ATT_HC_Api_Exception and surface via
  att_hc_api_error transient → admin notice
- Hard config-error gate at the top of the admin page blocks the UI when
  ATT_HC_API_URL/ATT_HC_API_KEY are missing or malformed

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-29 12:43:24 +01:00