getNearEvents compared only the sort column with >= / <= and excluded the
current event, using Id solely as a secondary ORDER BY. When several events
share a sort value - most often StartDateTime, which has one-second
resolution, with multiple monitors recording in step - the later event of
a tie chose the earlier one as its Next. Event playback, gapless or not,
then looped between the two and the Next button could not escape.
Compare the full (sort value, Id) tuple so ordering is strict in both
directions. Read the current event's sort value through the same
Events/Monitors join as the searches, which also makes Prev/Next work
when sorting by MonitorName (the value was previously looked up in the
Events row and was always NULL).
zm_update-1.39.31.sql gave monitors audio detection, but the level only ever
existed in shared memory, so it was gone the moment the frame passed and there
was nothing for the event view to plot. Add Frames.AudioLevel next to Score, on
the same 0-100 dBFS-derived scale the threshold uses.
What is stored is the peak since the previous row, not the level at the instant
the row was written. Frames rows are written well below the capture rate --
only alarm, bulk and score-increasing frames get one -- so sampling at write
time would drop exactly the short loud noises worth seeing on a timeline.
AudioDetector accumulates the peak as it decodes and Event::AddFrame takes it
where the row is built, which clears it so each row covers its own interval.
The Event constructor takes and discards it once, otherwise an event's first
row reports the loudest moment since the previous event ended.
This needs no shared memory change: zma is now an offline re-analysis tool and
the live analysis runs in a thread of zmc, alongside the capture thread that
runs the decoder, so the peak can stay in the AudioDetector. SharedData keeps
its documented 888-byte layout and its fixed offsets.
The frames ajax returns the column, and Score with it. elements in
web/ajax/status.php is a whitelist that never listed Score, which is why the
event view's cue strip has been reading an undefined Score off every frame.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JpiSWBmtQkR5bcgpHWY4ME
Apply validCardinal() to $_REQUEST['id'], consistent with the
validation now used in getNearFrame() and getFrameImage().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apply validCardinal() to $_REQUEST['id'] values, consistent with
getFrameImage() which already validates the same kind of inputs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The canView() failure path was dumping the entire Monitor object via
print_r() into the AJAX error response. This leaked all monitor
properties (Host, Path, ControlAddress, etc.) to users who fail the
permission check. Replace with a minimal error message containing
only the entity type and id.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The monitor entity's func/zmu elements (Status, FrameRate) execute
in the element loop before the post-SQL canView() check, allowing
any authenticated user to query any monitor via zmu commands.
Add an early permission check after the id is determined but before
the element processing loop. When the entity spec has an 'object'
key and an id is provided, construct the object and call canView()
which respects per-group and per-role permissions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace all four eval() call sites with call_user_func(). Entity-level
funcs (getFrameImage, getNearFrame, getNearEvents) are now callable
strings. Element-level funcs (daemonCheck, getLoad, getDiskPercent)
are now closures. postFunc handlers use call_user_func with $sqlData
passed as the argument.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The sort field whitelist check compared bare field names against
$fieldSql entries that could be backtick-wrapped or aliased SQL
expressions, so the check would often fail to match legitimate
fields. When it failed, execution fell through to append the sort
direction (ASC/DESC) without a field name, producing malformed SQL.
The error log call also passed the wrong variable to join().
Rewrite the sort handling to:
- Skip invalid fields with continue instead of falling through
- Match field names against bare, backtick-wrapped, and aliased forms
- Collect valid ORDER BY clauses into an array before appending
- Backtick-wrap field names in the output SQL
- Only append ORDER BY when valid clauses exist
Also parameterize the prev-event sort comparison in getNearEvents()
and add the missing sort_field bind parameter to both prev and next
event queries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(tag): Create tags on mobile
chore(tags): Change TagName to Name
chore(tags): eslint
chore(tags): dbFetchAll to dbQuery for removetag
chore(events): eslint (attempt 2)
feat(tags): Better handling of keyboard
fix(tags): Enter key for creating new tag
fix(tags): Don't allow space as a tag name
feat(tags): Delete tag if last assignment removed
fix(tags): Increase height of dropdown
in progress
fix(Tags): Use T.Id on the events page dropdown
fix(Tags): Remove $availableTags from events.php
chore(sql): Formatting sql statements
feat(Tags): Working OR on filters and events pages
fix(filter): Populate availableTags
chore(Tags): code formatting
fix(tag): Add tag on create tag
Fix(tags): Remove tag from available if last
feat(tags): Add zm_update.sql
fix(chosen): Undo css width
fix(chosen): tags dropdown width
fix(tags): dropdown over timeline
fix(tags): Full width input
fix(events): Refresh table on page show
chore(filter): Clean up availableTags
chore(event): Clean up available & selected Tags
fix(event): Update available tags on remove
fix(event): Remove hack for selected tags
feat(tags): Blur input after adding tag
doc(tags): Initial tags documentation
fix(tags): Dark theme dropdown
fix(tags): Dark theme for tags on input
fix(tags): Dark theme for highlight in dropdown
fix(tags): Populate filter tags droplist
chore(): Bump zm_update to 1.37.42
chore(tags): Move mobile check to skin.js
chore(tags): Comment debug statements
fix(tags): Enter key to create tag on mobile Chome
chore(tags): Space in 'All Tags' for translation
Temporary commit to handle cookie expiration times
chore(tags): Remove unnecessary Tag(s) from en_gb
chore(): Cleanup unnecessary Error and Debug
chore(): Resolve merge conflicts
chore(): Address merge conflicts with master