A CRUD Interface for managing report configs — saved table queries with an optional schedule and retention
A report config describes a report: what to query (source + query), over which window (range_sec), and optionally when to run it automatically (schedule) and how much run history to keep (retention). Configs are stored in the report system’s SQLite database; executing them into report runs is handled by the report run subsystem.
config_id — server-generated UUID, read-onlyname — mandatory, unique among existing configs (duplicate → 409); names of deleted configs may be reusedtype — mandatory; only "table" is supportedsource — mandatory; what the report queries (base, top_applications, …)query — mandatory JSON object, stored as received; its content is validated by the query pipeline when a run executesquery_structured — mandatory JSON object, the structured form of the filter the GUI built; stored as received and never interpreted by the facade. Its only member today is filter_clause, a flat array of condition / logical_operator / parenthesis elements. query and query_structured are a pair: a PUT body carrying either one must carry the otherrange_sec — mandatory, > 0; how far back the report window reaches — from the firing time for scheduled runs, from the system time for manual runskeep_empty_runs — optional bool, default true; when false, runs whose result is empty are discardedschedule — optional object {"expression": "<sec min hour dom month dow>"}, a cron expression evaluated in UTC, rejected if it can fire more often than once per 15 minutes; the seconds field must be 0 (schedule resolution is one minute); absent = manual-only configretention — optional object {"max_runs": <n>, "policy": "rotate"}, max_runs in 1..30 (max_retention_runs); absent = no history limit. Retention is best-effort: it bounds how many runs a config keeps, but the disk-derived storage limit can evict older runs sooner, so on a small disk a config may end up with fewer than max_runsenabled — mandatory bool; false = the schedule is pausedcreated_at_sec / updated_at_sec — server-generated unix timestamps, read-onlylast_run — read-only object describing the newest run of this config, in the same shape as a report run; absent until the config is executed for the first time. It follows the run history: deleting the newest run moves it to the previous one, deleting the whole history removes it. Executing a config is not a user edit, so a run never moves updated_at_secconfig_id:
Path: /api/chf/report_configs/{config_id}
Method: GET
Response:
{
"message": "Success",
"data": {
"config_id": "77ab19c2-f04d-5be6-a8c3-1d90e2f45a11",
"name": "daily-bittorrent",
"type": "table",
"source": "top_applications",
"query": {
"filter_clause": "application = 'BitTorrent'",
"group_by": ["application"],
"selected_columns": ["update_total_bytes"],
"order_by": "update_total_bytes",
"sort_order": "desc",
"limit": 100
},
"query_structured": {
"filter_clause": [
{"type": "condition", "column_name": "application", "operator": "=", "value": ["BitTorrent"]}
]
},
"range_sec": 86400,
"keep_empty_runs": true,
"schedule": {"expression": "0 10 0 * * *"},
"retention": {"max_runs": 30, "policy": "rotate"},
"enabled": true,
"created_at_sec": 1785196812,
"updated_at_sec": 1785199001,
"last_run": {
"run_id": "9f2c4b81-d7e6-a2f0-c3b5-d8e1a4f7c0b3",
"config_id": "77ab19c2-f04d-5be6-a8c3-1d90e2f45a11",
"name": "daily-bittorrent",
"status": "failed",
"origin": "scheduled",
"from_sec": 1785024000,
"to_sec": 1785110400,
"started_at_sec": 1785110410,
"finished_at_sec": 1785110530,
"error": "query timeout after 120 seconds"
}
}
}/api/chf/report_configsGETdata is an array of the objects above/api/chf/report_configsPOSTconfig_id:
/api/chf/report_configs/{config_id}PUTPOST fields; if type or source is present, query must be present too, and query and query_structured must always be sent togetherconfig_id never changesconfig_id:
Path: /api/chf/report_configs/{config_id}
Method: DELETE
Response:
{
"message": "The report config has been deleted",
"data": null
}Path: /api/chf/report_configs
Method: DELETE
Response:
{
"message": "All report configs have been deleted",
"data": null
}