Guides image + draft gating, audit retention, ADR-015 gate, backup credential redaction
This commit is contained in:
@@ -1835,3 +1835,75 @@ def catalog_rename(table, source, target, apply_changes):
|
||||
{'t': target, 'k': sourcerow[0]})
|
||||
|
||||
click.echo(click.style(f'Renamed {source!r} to {target!r}.', fg='green'))
|
||||
|
||||
|
||||
@click.group('audit')
|
||||
def audit_cli():
|
||||
"""Audit log maintenance."""
|
||||
pass
|
||||
|
||||
|
||||
@audit_cli.command('stats')
|
||||
@with_appcontext
|
||||
def audit_stats():
|
||||
"""Show how big the audit log is and what retention is configured."""
|
||||
from shopdb.core.services import auditretention
|
||||
|
||||
days = auditretention.configureddays()
|
||||
info = auditretention.stats(days or None)
|
||||
|
||||
click.echo(f"Entries: {info['total']:,}")
|
||||
click.echo(f"Oldest: {info['oldest'] or '-'}")
|
||||
click.echo(f"Newest: {info['newest'] or '-'}")
|
||||
if days:
|
||||
click.echo(f"Retention: {days} days")
|
||||
click.echo(f"Expired: {info['expired']:,} entry(s) older than that")
|
||||
else:
|
||||
click.echo('Retention: not set - nothing is ever deleted')
|
||||
click.echo("Set one in Settings > Site & Facility, or pass --days here.")
|
||||
|
||||
|
||||
@audit_cli.command('purge')
|
||||
@click.option('--days', type=int, default=None,
|
||||
help='Override the configured retention for this run.')
|
||||
@click.option('--apply', 'apply_changes', is_flag=True,
|
||||
help='Actually delete. Without this the command only reports.')
|
||||
@with_appcontext
|
||||
def audit_purge(days, apply_changes):
|
||||
"""Delete audit entries older than the retention window.
|
||||
|
||||
Reports by default and deletes only with --apply, because this is the one
|
||||
command in the app that destroys history a site may be keeping deliberately.
|
||||
"""
|
||||
from shopdb.core.services import auditretention
|
||||
|
||||
days = days if days is not None else auditretention.configureddays()
|
||||
if not days:
|
||||
click.echo(click.style(
|
||||
'No retention configured, so there is nothing to purge. Set '
|
||||
'audit_retentiondays in Settings > Site & Facility, or pass --days.',
|
||||
fg='yellow'))
|
||||
return
|
||||
|
||||
preview = auditretention.purge(days, dryrun=True)
|
||||
click.echo(f"Retention {days} days - cutoff {preview['cutoff']}")
|
||||
click.echo(f"{preview['matched']:,} entry(s) are older than that.")
|
||||
|
||||
if not preview['matched']:
|
||||
return
|
||||
if not apply_changes:
|
||||
click.echo(click.style(
|
||||
'Dry run. Nothing was deleted. Re-run with --apply to write.',
|
||||
fg='yellow'))
|
||||
return
|
||||
|
||||
# No batch cap here: unlike the API this is not behind a proxy timeout, and
|
||||
# a purge that stops halfway leaves the operator wondering which half.
|
||||
result = auditretention.purge(days)
|
||||
click.echo(click.style(
|
||||
f"Deleted {result['deleted']:,} entry(s) in {result['batches']} batch(es).",
|
||||
fg='green'))
|
||||
if not result['complete']:
|
||||
click.echo(click.style(
|
||||
f"{result['remaining']:,} still older than the cutoff - re-run.",
|
||||
fg='yellow'))
|
||||
|
||||
Reference in New Issue
Block a user