Development

This commit is contained in:
Matthew Grotke 2026-05-25 20:46:17 -04:00
parent adcfe55c7c
commit 59d3d65d18
7 changed files with 146 additions and 75 deletions

View file

@ -594,22 +594,27 @@ def collect_tokens():
pending_items = get_dashboard_pending()
if pending_items:
rows = ''
_tr_onclick = (
'onclick="if(event.target.type!==\'checkbox\')'
'this.nextElementSibling.hidden=!this.nextElementSibling.hidden"'
)
for _uuid, ts, _cmd, user in pending_items:
snap = load_snapshot_for_uuid(_uuid)
dt_str = datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M')
snap_desc = e(snap['description']) if snap else ''
before_html = _render_snap_val(snap.get('before') if snap else None)
after_html = _render_snap_val(snap.get('after') if snap else None)
snap = load_snapshot_for_uuid(_uuid)
dt_str = datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M')
snap_desc = e(snap['description']) if snap else ''
before_val = snap.get('before') if snap else None
after_val = snap.get('after') if snap else None
snap_id = e(_uuid[:8]) if snap else ''
rows += (f'<tr>'
f'<td class="table-cell"><input type="checkbox" name="selected_uuids" value="{e(_uuid)}"/></td>'
f'<td class="table-cell">{e(dt_str)}</td>'
f'<td class="table-cell">{snap_desc}</td>'
f'<td class="table-cell">{before_html}</td>'
f'<td class="table-cell">{after_html}</td>'
f'<td class="table-cell">{snap_id}</td>'
f'<td class="table-cell">{e(user)}</td>'
f'</tr>')
rows += (f'<tr style="cursor:pointer" {_tr_onclick}>'
f'<td class="table-cell"><input type="checkbox" name="selected_uuids" value="{e(_uuid)}"/></td>'
f'<td class="table-cell">{e(dt_str)}</td>'
f'<td class="table-cell">{snap_desc}</td>'
f'<td class="table-cell">{_render_snap_val(before_val)}</td>'
f'<td class="table-cell">{_render_snap_val(after_val)}</td>'
f'<td class="table-cell">{snap_id}</td>'
f'<td class="table-cell">{e(user)}</td>'
f'</tr>'
f'{_snap_expand_row(before_val, after_val, 7)}')
select_all = (
'<input type="checkbox" '
'onchange="document.querySelectorAll(\'[name=selected_uuids]\').forEach(c=>c.checked=this.checked)"/>'
@ -636,25 +641,27 @@ def collect_tokens():
done_items = get_dashboard_done()
if done_items:
hist_rows = ''
_hist_onclick = 'onclick="this.nextElementSibling.hidden=!this.nextElementSibling.hidden"'
for _uuid, applied_ts in done_items:
snap = load_snapshot_for_uuid(_uuid)
if applied_ts:
dt_str = datetime.fromtimestamp(applied_ts).strftime('%Y-%m-%d %H:%M')
else:
dt_str = '-'
snap_desc = e(snap['description']) if snap else ''
before_html = _render_snap_val(snap.get('before') if snap else None)
after_html = _render_snap_val(snap.get('after') if snap else None)
snap_id = e(_uuid[:8]) if snap else ''
snap_user = e(snap['user']) if snap else ''
hist_rows += (f'<tr>'
snap_desc = e(snap['description']) if snap else ''
before_val = snap.get('before') if snap else None
after_val = snap.get('after') if snap else None
snap_id = e(_uuid[:8]) if snap else ''
snap_user = e(snap['user']) if snap else ''
hist_rows += (f'<tr style="cursor:pointer" {_hist_onclick}>'
f'<td class="table-cell">{e(dt_str)}</td>'
f'<td class="table-cell">{snap_desc}</td>'
f'<td class="table-cell">{before_html}</td>'
f'<td class="table-cell">{after_html}</td>'
f'<td class="table-cell">{_render_snap_val(before_val)}</td>'
f'<td class="table-cell">{_render_snap_val(after_val)}</td>'
f'<td class="table-cell">{snap_id}</td>'
f'<td class="table-cell">{snap_user}</td>'
f'</tr>')
f'</tr>'
f'{_snap_expand_row(before_val, after_val, 6)}')
history_html = (
'<table class="data-table" style="margin-bottom:1rem">'
'<thead><tr>'
@ -813,25 +820,42 @@ def e(text):
return html_mod.escape(str(text))
def _render_snap_val(val):
"""Return an HTML string for a snapshot before/after cell value."""
def _snap_text(val):
"""Return the plain-text representation of a snapshot before/after value."""
if val is None:
return ''
if isinstance(val, dict) and len(val) == 1:
k, v = next(iter(val.items()))
text = f'{k}: {v}'
elif isinstance(val, (dict, list)):
text = json.dumps(val, separators=(',', ':'))
else:
text = str(val)
return f'{k}: {v}'
if isinstance(val, (dict, list)):
return json.dumps(val, separators=(',', ':'))
return str(val)
def _render_snap_val(val):
"""Return truncated escaped HTML for a snapshot before/after table cell."""
text = _snap_text(val)
if not text:
return ''
trunc = (text[:23] + '') if len(text) > 24 else text
if trunc == text:
return e(text)
return (f'<details style="display:inline">'
f'<summary style="cursor:pointer;list-style:none">{e(trunc)}</summary>'
f'<pre style="margin:0.5rem 0;white-space:pre-wrap;font-size:0.85em">'
f'{e(json.dumps(val, indent=2) if isinstance(val, (dict, list)) else text)}'
f'</pre></details>')
return e(trunc)
def _snap_expand_row(before_val, after_val, colspan):
"""Return a hidden <tr> that expands with full before/after content."""
pre = ('max-height:200px;overflow-y:auto;white-space:pre-wrap;'
'font-size:0.85em;background:#fff;border:1px solid #ddd;'
'padding:0.5rem;margin:0.25rem 0')
def box(label, val):
text = _snap_text(val) if val is not None else ''
if isinstance(val, (dict, list)):
text = json.dumps(val, indent=2)
body = e(text) if text else '<em>(none)</em>'
return f'<div style="flex:1;min-width:0"><strong>{label}</strong><pre style="{pre}">{body}</pre></div>'
inner = f'<div style="display:flex;gap:1rem">{box("Before", before_val)}{box("After", after_val)}</div>'
return (f'<tr hidden>'
f'<td colspan="{colspan}" style="padding:0.5rem 1rem;background:#f8f8f8">'
f'{inner}</td></tr>')
def apply_tokens(text, tokens):