Document the PHP api layer and fix what documenting it exposed
Writing down what each endpoint actually guarantees made the places it didn't obvious — shell arguments reaching a crontab or a bash -c unescaped, master.conf written without tmp+rename, and conf edits that could be saved without ever being parsed.
This commit is contained in:
@@ -1,4 +1,57 @@
|
||||
<?php
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
// PURPOSE
|
||||
// Recent run history. The last 24 finished job runs across every orchestrator and script,
|
||||
// newest first, with status and duration — the activity feed on the scheduler page.
|
||||
//
|
||||
// OPERATIONAL MODEL
|
||||
// There is no run database. Each job writes a .json stat file beside its log, and this
|
||||
// endpoint reconstructs history by walking LOG_DIR recursively and sorting what it finds by
|
||||
// start time. That keeps the stat file the single source of truth for a job's outcome —
|
||||
// the same file status.php reads for live state — instead of maintaining a second record
|
||||
// that could disagree with it.
|
||||
//
|
||||
// DESIGN PRINCIPLES
|
||||
// Finished runs only.
|
||||
// status === 'running' is skipped, because a run in progress has no duration and
|
||||
// belongs to status.php's live indicators, not to history. The two endpoints partition
|
||||
// the same files rather than overlapping.
|
||||
//
|
||||
// Capped at 24 after sorting, not before.
|
||||
// Every stat file is read and ranked before the slice, so the newest 24 are genuinely
|
||||
// the newest — filesystem iteration order says nothing about run time.
|
||||
//
|
||||
// Duration is derived, never stored.
|
||||
// end - start is computed here, so a stat file that recorded a start but never got to
|
||||
// write an end still yields a usable row rather than being discarded.
|
||||
//
|
||||
// OPERATIONAL SAFEGUARDS
|
||||
// Read-only. Nothing here deletes a stat file, truncates a log, or re-runs a job.
|
||||
//
|
||||
// The whole walk is wrapped in a try/catch.
|
||||
// RecursiveDirectoryIterator throws when LOG_DIR does not exist or a subdirectory is
|
||||
// unreadable — on a fresh install that is the normal state, not an error. The catch
|
||||
// yields an empty run list so the page renders "no runs yet" instead of a 500.
|
||||
//
|
||||
// Every individual file read is independently suppressed and validated.
|
||||
// @file_get_contents, @json_decode, then an is_array plus required-key check. A stat
|
||||
// file caught mid-write, truncated, or left over from an older schema is skipped —
|
||||
// one bad file cannot cost the other 23 rows.
|
||||
//
|
||||
// Negative durations are clamped.
|
||||
// max(0, end - start) — a stat file whose clock went backwards across an NTP step
|
||||
// reports 0s rather than a negative duration the UI would have to special-case.
|
||||
//
|
||||
// REQUEST
|
||||
// GET, no parameters
|
||||
//
|
||||
// RESPONSE
|
||||
// {"ok":true,"runs":[{"id","label","status","start","dur"}, …]} newest first, max 24
|
||||
//
|
||||
// DEPENDS ON
|
||||
// include/config.php LOG_DIR
|
||||
// LOG_DIR/**/*.json stat files written by run_job.sh
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
header('Content-Type: application/json');
|
||||
require_once dirname(__DIR__) . '/include/config.php';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user