Files view: change details with before/after images (v2.4.0)

- the timeline's Δ glyph became a **Details** button, and it now appears on
  Update rows too (not just card-side changes); EN "Changed (card)" ->
  "Changed on card", RU "Изменено (картой)" -> "Изменено картой"
- the details modal shows the affected unit — the whole file, or the single
  record that changed — **before and after** the change as hex views (16 bytes
  per row, ASCII column, never-observed bytes as `--`), with the bytes that
  changed and the bytes the operation *first observed* highlighted in two
  different colours, plus the changed/new byte counts and the APDU reference;
  the old before/after byte table is gone
- `GET /api/sessions/<id>/files/image?key=…&change=…` returns those two views
  (segments with offsets) plus the change's metadata; the images are
  reconstructed from the current image by undoing the later changes
  (`FileTracker.change_images`), so nothing is duplicated per event — every
  value change is an event, hence the values are exact (a byte only first
  observed later can appear as known in both views)
- tracker: change events carry `observed_ranges` (bytes that were unknown
  before the operation) next to `ranges`; an update that only observes new
  bytes now records an event (so it can be inspected); record-file ranges are
  in-record byte offsets (the record number stays in its own field), which the
  previous "record number as position" could not express for a hex highlight
- tests: change-images reconstruction (before/after per event, later changes
  undone, records isolated, observation-only events, unknown gaps), the image
  endpoint (200/404/400), and Node tests for the hex-image helper (highlight
  classes, unknown gaps, ASCII, row limit) and the modal rendering
- v2.4.0; SW cache simtrace-analyser-v65
This commit is contained in:
catarrh committed 2026-10-17 21:19:14 +03:00
1 parent 87b6811adf
commit cad3999307
12 files changed
+617 -120

No files matched your search

+26
View File
@@ -141,6 +141,9 @@ class RequestHandler(BaseHTTPRequestHandler):
elif path.startswith('/api/sessions/') and path.endswith('/pcap'):
session_id = int(path.split('/')[-2])
self._handle_get_session_pcap(session_id)
elif path.startswith('/api/sessions/') and path.endswith('/files/image'):
session_id = int(path.split('/')[-3])
self._handle_session_files_image(session_id, params)
elif path.startswith('/api/sessions/') and path.endswith('/files'):
session_id = int(path.split('/')[-2])
self._handle_session_files(session_id)
@@ -475,6 +478,29 @@ class RequestHandler(BaseHTTPRequestHandler):
f'files={len(data["files"])} operations={data["timeline_total"]}')
self._send_json(data)
def _handle_session_files_image(self, session_id, params):
"""Before/after images of one change (the Files view's Details)."""
session = self.db.get_session(session_id)
if session is None:
self._send_error(404, 'Session not found')
return
key = (params.get('key', [''])[0]).strip()
try:
idx = int(params.get('change', ['0'])[0])
except ValueError:
self._send_error(400, 'change must be an index')
return
if not key or idx < 0:
self._send_error(400, 'key and change are required')
return
data = self.db.change_images(session_id, key, idx)
if data is None:
self._send_error(404, 'No such file or change')
return
_log(f'Session file change: session={session_id} key={key} '
f'change={idx} op={data["op"]} index={data["index"]}')
self._send_json(data)
def _handle_rename_session(self, session_id, name):
self.db.rename_session(session_id, name)
self._send_json({'ok': True})