11 Commits

Author SHA1 Message Date
josh 5b3edbdfe5 Merge pull request 'chore: bump to version 1.7.2' (#79) from feat/cap-job-runs-history into dev
CI / test (push) Successful in 13s
CI / build-dev (push) Successful in 20s
CI / test (pull_request) Successful in 18s
CI / build-dev (pull_request) Has been skipped
Reviewed-on: #79
2026-06-06 09:32:21 -04:00
josh 8edbeba2ec Merge branch 'dev' into feat/cap-job-runs-history
CI / test (pull_request) Successful in 8s
CI / build-dev (pull_request) Has been skipped
2026-06-06 09:31:18 -04:00
josh ca914b915b chore: bump to version 1.7.2
CI / test (pull_request) Successful in 14s
CI / build-dev (pull_request) Has been skipped
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 09:30:23 -04:00
josh 53fbcbe22c Merge pull request 'feat: cap job_runs history at last 10 per job' (#78) from feat/cap-job-runs-history into dev
CI / test (push) Successful in 19s
CI / build-dev (push) Successful in 21s
Reviewed-on: #78
2026-06-06 09:27:05 -04:00
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 72b8d60985 Merge pull request 'Merge pull request 'test: add failing tests for sort/order on GET /api/instances' (#71) from dev into main' (#76) from merge/main-into-dev-v1.7.1 into dev
CI / test (push) Successful in 12s
CI / test (pull_request) Successful in 15s
CI / build-dev (push) Successful in 15s
CI / build-dev (pull_request) Has been skipped
Reviewed-on: #76
2026-05-30 19:03:24 -04:00
josh 917fb26e05 Merge branch 'main' into dev — resolve 1.7.0/1.7.1 version overlap, keep 1.7.1
CI / test (pull_request) Successful in 11s
CI / build-dev (pull_request) Has been skipped
# Conflicts:
#	package.json
2026-05-30 19:00:49 -04:00
josh a28867b398 Merge pull request 'fix: base64-encode Patchmon Basic auth credentials server-side' (#74) from fix/patchmon-sync-basic-auth into dev
CI / test (push) Successful in 9s
CI / build-dev (push) Successful in 23s
CI / test (pull_request) Successful in 18s
CI / build-dev (pull_request) Has been skipped
Reviewed-on: #74
2026-05-30 18:54:53 -04:00
josh aa6e28d818 Merge pull request 'chore: bump to version 1.7.0' (#72) from chore/bump-v1.7.0 into main
CI / test (push) Successful in 13s
Release / release (push) Successful in 30s
CI / build-dev (push) Has been skipped
Reviewed-on: #72
2026-05-29 16:10:56 -04:00
josh 2cf797545c chore: bump to version 1.7.0
CI / test (pull_request) Successful in 14s
CI / build-dev (pull_request) Has been skipped
2026-05-29 16:08:18 -04:00
josh a0381b12cc Merge pull request 'test: add failing tests for sort/order on GET /api/instances' (#71) from dev into main
CI / test (push) Successful in 16s
Release / release (push) Failing after 11s
CI / build-dev (push) Has been skipped
Reviewed-on: #71
2026-05-29 16:05:54 -04:00
4 changed files with 38 additions and 4 deletions
+1 -1
View File
@@ -1 +1 @@
const VERSION = "1.7.1";
const VERSION = "1.7.2";
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "catalyst",
"version": "1.7.1",
"version": "1.7.2",
"type": "module",
"scripts": {
"start": "node server/server.js",
+23 -2
View File
@@ -6,6 +6,8 @@ import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DEFAULT_PATH = join(__dirname, '../data/catalyst.db');
const JOB_RUN_LIMIT = 10;
let db;
function init(path) {
@@ -17,7 +19,7 @@ function init(path) {
db.exec('PRAGMA foreign_keys = ON');
db.exec('PRAGMA synchronous = NORMAL');
createSchema();
if (path !== ':memory:') { seed(); seedJobs(); }
if (path !== ':memory:') { seed(); seedJobs(); pruneAllJobRuns(); }
}
function createSchema() {
@@ -267,6 +269,7 @@ export function importJobs(jobRows, jobRunRows = []) {
`);
for (const r of jobRunRows) insertRun.run(r);
}
pruneAllJobRuns();
db.exec('COMMIT');
}
@@ -326,10 +329,28 @@ export function completeJobRun(runId, status, result) {
db.prepare(`
UPDATE job_runs SET ended_at=strftime('%Y-%m-%dT%H:%M:%f', 'now'), status=@status, result=@result WHERE id=@id
`).run({ id: runId, status, result });
const row = db.prepare('SELECT job_id FROM job_runs WHERE id = ?').get(runId);
if (row) pruneJobRuns(row.job_id);
}
export function getJobRuns(jobId) {
return db.prepare('SELECT * FROM job_runs WHERE job_id = ? ORDER BY id DESC').all(jobId);
return db.prepare(`SELECT * FROM job_runs WHERE job_id = ? ORDER BY id DESC LIMIT ${JOB_RUN_LIMIT}`).all(jobId);
}
function pruneJobRuns(jobId) {
db.prepare(`
DELETE FROM job_runs
WHERE job_id = ?
AND id NOT IN (
SELECT id FROM job_runs WHERE job_id = ? ORDER BY id DESC LIMIT ?
)
`).run(jobId, jobId, JOB_RUN_LIMIT);
}
function pruneAllJobRuns() {
for (const j of db.prepare('SELECT id FROM jobs').all()) {
pruneJobRuns(j.id);
}
}
// ── Test helpers ──────────────────────────────────────────────────────────────
+13
View File
@@ -450,4 +450,17 @@ describe('job_runs', () => {
expect(runs[0].id).toBe(r2);
expect(runs[1].id).toBe(r1);
});
it('caps history at the last 10 runs per job', () => {
createJob(baseJob);
const id = getJobs()[0].id;
for (let i = 0; i < 15; i++) {
const runId = createJobRun(id);
completeJobRun(runId, 'success', `run ${i}`);
}
const runs = getJobRuns(id);
expect(runs).toHaveLength(10);
expect(runs[0].result).toBe('run 14');
expect(runs[9].result).toBe('run 5');
});
});