Commit Graph

15 Commits

Author SHA1 Message Date
josh e330119753 feat: cap job_runs history at last 10 per job
CI / test (pull_request) Successful in 13s
CI / build-dev (pull_request) Has been skipped
Tailscale, Patchmon, and Semaphore sync jobs all wrote into a shared
job_runs table with no retention. With default poll intervals of 15-60
minutes, history grew unbounded.

- Add pruneJobRuns(jobId) and pruneAllJobRuns() helpers.
- Prune after every completeJobRun() so new runs trim old ones.
- Prune once on init() to clean up existing over-cap rows.
- Prune in importJobs() so re-imported runs are also capped.
- Defensive LIMIT 10 in getJobRuns() for the read path.

No UI changes needed — _renderRunList already renders whatever the
server returns. No schema migration — only row deletions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-05 23:38:43 -04:00
Josh Wright b6ca460ac6 feat: add sort by vmid, name, last created, last updated on dashboard
CI / test (pull_request) Successful in 9s
CI / build-dev (pull_request) Has been skipped
- GET /api/instances now accepts ?sort= (name|vmid|created_at|updated_at)
  and ?order= (asc|desc); invalid sort fields fall back to name asc
- Timestamp sorts use id as a tiebreaker (datetime() precision is 1 s)
- Toolbar gains a sort-field <select> and a ↑/↓ direction toggle button
- toggleSortDir() flips direction and re-fetches; state held in data-dir attr

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 08:26:46 -04:00
josh 7999f46ca2 fix: millisecond precision timestamps and correct history ordering
CI / test (pull_request) Successful in 21s
CI / build-dev (pull_request) Has been skipped
datetime('now') only stores to the second, making same-second events
indistinguishable. Switched all instance_history and job_runs writes
to strftime('%Y-%m-%dT%H:%M:%f', 'now') for millisecond precision.

Reverted getInstanceHistory to ORDER BY changed_at DESC, id DESC so
newest events appear at the top and instance creation (lowest id,
earliest timestamp) is always at the bottom.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 20:19:42 -04:00
josh 8f35724bde fix: queue on-create jobs sequentially and fix history ordering
CI / test (pull_request) Successful in 13s
CI / build-dev (pull_request) Has been skipped
runJobsOnCreate now awaits each job before starting the next,
ensuring they don't stomp each other's DB writes in parallel.

getInstanceHistory changed to ORDER BY changed_at ASC, id ASC so
the creation event (lowest id) is always first regardless of
same-second timestamps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 20:09:32 -04:00
josh 954d85ca81 feat: include job config and run history in export/import backup
CI / test (pull_request) Successful in 16s
CI / build-dev (pull_request) Has been skipped
Export bumped to version 3, now includes jobs (with raw unmasked
config) and job_runs arrays. Import restores them when present and
restarts the scheduler. Payloads without a jobs key leave jobs
untouched, keeping v1/v2 backups fully compatible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 19:43:34 -04:00
josh a934db1a14 feat: add Semaphore Sync job
CI / test (pull_request) Successful in 15s
CI / build-dev (pull_request) Has been skipped
Fetches Semaphore project inventory via Bearer auth, parses the
Ansible INI format to extract hostnames, and sets semaphore=1/0
on matching instances.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 19:34:45 -04:00
josh 0b350f3b28 feat: add Patchmon Sync job
CI / test (pull_request) Successful in 14s
CI / build-dev (pull_request) Has been skipped
Syncs patchmon field on instances by querying the Patchmon hosts API
and matching hostnames. API token masked as REDACTED in responses.
seedJobs now uses INSERT OR IGNORE so new jobs are seeded on existing
installs without re-running the full seed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 19:22:41 -04:00
josh d7727badb1 feat: jobs system with dedicated nav page and run history
CI / test (pull_request) Successful in 14s
CI / build-dev (pull_request) Has been skipped
Replaces ad-hoc Tailscale config tracking with a proper jobs system.
Jobs get their own nav page (master/detail layout), a dedicated DB
table, and full run history persisted forever. Tailscale connection
settings move from the Settings modal into the Jobs page. Registry
pattern makes adding future jobs straightforward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 19:09:42 -04:00
josh 47e9c4faf7 feat: Tailscale sync jobs
CI / test (pull_request) Successful in 14s
CI / build-dev (pull_request) Has been skipped
Adds a background job system that polls the Tailscale API on a configurable
interval and syncs tailscale status and IPs to instances by hostname match.

- New config table (key/value) in SQLite for persistent server-side settings
- New server/jobs.js: runTailscaleSync + restartJobs scheduler
- GET/PUT /api/config — read and write Tailscale settings; API key masked as **REDACTED** on GET
- POST /api/jobs/tailscale/run — immediate manual sync
- Settings modal: new Tailscale Sync section with enable toggle, tailnet, API key, poll interval, Save + Run Now buttons, last-run status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 17:11:40 -04:00
josh 218cdb08c5 feat: include history in export/import backup
CI / test (pull_request) Successful in 15s
CI / build-dev (pull_request) Has been skipped
Export now returns version 2 with a history array alongside instances.
Import accepts the history array and restores all audit events. v1 backups
without a history key still import cleanly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 16:04:53 -04:00
josh d17f364fc5 fix: clear instance history on delete and import
CI / test (pull_request) Successful in 14s
CI / build-dev (pull_request) Has been skipped
deleteInstance now removes history rows for that vmid before removing
the instance. importInstances clears all history before replacing
instances. Prevents stale history appearing when a vmid is reused.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 15:37:45 -04:00
josh cb01573cdf feat: audit log / history timeline on instance detail page
CI / test (pull_request) Successful in 13s
CI / build-dev (pull_request) Has been skipped
Adds an instance_history table that records every field change:
- createInstance logs a 'created' event
- updateInstance diffs old vs new and logs one row per changed field
  (name, state, stack, vmid, tailscale_ip, all service flags)
- History is stored under the new vmid when vmid changes

New endpoint: GET /api/instances/:vmid/history

The 'timestamps' section on the detail page is replaced with a
grid timeline showing timestamp | field | old → new for each event.
State changes are colour-coded (deployed=green, testing=amber,
degraded=red). Boolean service flags display as on/off.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 14:35:35 -04:00
josh af207339a4 feat: settings modal with database export and import
CI / test (pull_request) Successful in 12s
CI / build-dev (pull_request) Has been skipped
Adds a gear button to the nav that opens a settings modal with:
- Export: GET /api/export returns all instances as a JSON backup file
  with a Content-Disposition attachment header
- Import: POST /api/import validates and bulk-replaces all instances;
  client uses FileReader to POST the parsed JSON, with a confirm dialog
  before destructive replace

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 14:10:59 -04:00
josh 6c04a30c3a fix: skip db boot init in test env to prevent parallel worker lock
CI / test (pull_request) Successful in 9m29s
CI / build-dev (pull_request) Has been skipped
Vitest runs test files in parallel workers. Each worker imports server/db.js,
which triggered module-level init(DEFAULT_PATH) unconditionally. Two workers
racing to open the same SQLite file caused "database is locked", followed
by process.exit(1) killing the worker — surfacing as:

  Error: process.exit unexpectedly called with "1"

Fix: guard the boot init block behind NODE_ENV !== 'test'. Vitest sets
NODE_ENV=test automatically. Each worker's beforeEach(() => _resetForTest())
initialises its own :memory: database, so no file coordination is needed.

process.exit(1) is also guarded by the same condition — it must never
fire inside a test runner process.

TDD: two regression tests added to tests/db.test.js documenting the
expected boot behaviour and proving the module loads cleanly in parallel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 11:31:55 -04:00
josh 6e40413385 claude went crazy
Build / test (push) Successful in 9m28s
Build / release (push) Successful in 1s
Build / build (push) Successful in 25s
2026-03-28 02:35:00 -04:00