|
42 | 42 | db_get_keys, db_insert_report, db_get_recent_results2, db_patch_approval2, db_get_report, |
43 | 43 | db_ensure_schema, db_get_apikeys, db_update_apikey, db_filter_apikeys, db_clear_delegates, |
44 | 44 | db_find_subjects, db_insert_result2, db_get_relevant_results2, db_add_delegate, db_get_group, |
45 | | - db_filter_accounts, |
| 45 | + db_filter_accounts, db_get_report_history, |
46 | 46 | ) |
47 | 47 |
|
48 | 48 |
|
@@ -84,6 +84,7 @@ def __init__(self): |
84 | 84 | ROLES = {'read_any': 1, 'append_any': 2, 'admin': 4, 'approve': 8} |
85 | 85 | # number of days that expired results will be considered in lieu of more recent, but unapproved ones |
86 | 86 | GRACE_PERIOD_DAYS = 7 |
| 87 | +HISTORY_LIMIT = 500 |
87 | 88 | # separator between signature and report data; use something like |
88 | 89 | # ssh-keygen \ |
89 | 90 | # -Y sign -f ~/.ssh/id_ed25519 -n report myreport.yaml |
@@ -122,7 +123,12 @@ class ViewType(Enum): |
122 | 123 | ViewType.fragment: 'scope.md', |
123 | 124 | ViewType.page: 'overview.html', |
124 | 125 | } |
125 | | -REQUIRED_TEMPLATES = tuple(set(fn for view in (VIEW_REPORT, VIEW_DETAIL, VIEW_TABLE, VIEW_SCOPE) for fn in view.values())) |
| 126 | +VIEW_HISTORY = { |
| 127 | + ViewType.markdown: 'history.md', |
| 128 | + ViewType.fragment: 'history.md', |
| 129 | + ViewType.page: 'overview.html', |
| 130 | +} |
| 131 | +REQUIRED_TEMPLATES = tuple(set(fn for view in (VIEW_REPORT, VIEW_DETAIL, VIEW_TABLE, VIEW_SCOPE, VIEW_HISTORY) for fn in view.values())) |
126 | 132 |
|
127 | 133 |
|
128 | 134 | # do I hate these globals, but I don't see another way with these frameworks |
@@ -580,8 +586,9 @@ def render_view(view, view_type, detail_page='detail', base_url='/', title=None, |
580 | 586 | stage1 = view[ViewType.fragment] |
581 | 587 | def scope_url(uuid): return f"{base_url}page/scope/{uuid}" # noqa: E306,E704 |
582 | 588 | def detail_url(subject, scope): return f"{base_url}page/{detail_page}/{subject}/{scope}" # noqa: E306,E704 |
| 589 | + def history_url(subject, scope): return f"{base_url}page/history/{subject}/{scope}" # noqa: E306,E704 |
583 | 590 | def report_url(report, *args, **kwargs): return _build_report_url(base_url, report, *args, **kwargs) # noqa: E306,E704 |
584 | | - fragment = templates_map[stage1].render(base_url=base_url, detail_url=detail_url, report_url=report_url, scope_url=scope_url, **kwargs) |
| 591 | + fragment = templates_map[stage1].render(base_url=base_url, detail_url=detail_url, history_url=history_url, report_url=report_url, scope_url=scope_url, **kwargs) |
585 | 592 | if view_type != ViewType.markdown and stage1.endswith('.md'): |
586 | 593 | fragment = markdown(fragment, extensions=['extra']) |
587 | 594 | if stage1 != stage2: |
@@ -702,6 +709,24 @@ async def get_detail_full( |
702 | 709 | ) |
703 | 710 |
|
704 | 711 |
|
| 712 | +@app.get("/{view_type}/history/{subject}/{scopeuuid}") |
| 713 | +async def get_history( |
| 714 | + request: Request, |
| 715 | + conn: Annotated[connection, Depends(get_conn)], |
| 716 | + view_type: ViewType, |
| 717 | + subject: str, |
| 718 | + scopeuuid: str, |
| 719 | +): |
| 720 | + scopes = get_scopes() |
| 721 | + scope_name = scopes[scopeuuid]['name'] if scopeuuid in scopes else scopeuuid |
| 722 | + with conn.cursor() as cur: |
| 723 | + history = db_get_report_history(cur, subject, scopeuuid, limit=HISTORY_LIMIT) |
| 724 | + return render_view( |
| 725 | + VIEW_HISTORY, view_type, history=history, subject=subject, scope_name=scope_name, |
| 726 | + history_limit=HISTORY_LIMIT, base_url=settings.base_url, title=f'📜 Compliance history for {subject}', |
| 727 | + ) |
| 728 | + |
| 729 | + |
705 | 730 | @app.get("/{view_type}/table") |
706 | 731 | async def get_table( |
707 | 732 | request: Request, |
|
0 commit comments