Merge branch 'master' of github.com:ZoneMinder/zoneminder

master
Isaac Connor 2025-06-13 09:34:36 -04:00
commit 657af6a628
8 changed files with 376 additions and 214 deletions

View File

@ -46,7 +46,7 @@ BuildRequires: polkit-devel
BuildRequires: cmake
BuildRequires: gnutls-devel
BuildRequires: bzip2-devel
BuildRequires: pcre-devel
BuildRequires: pcre2-devel
BuildRequires: libjpeg-turbo-devel
BuildRequires: findutils
BuildRequires: coreutils
@ -246,6 +246,9 @@ ln -s ../../../../../../../..%{_sysconfdir}/pki/tls/certs/ca-bundle.crt %{buildr
# Handle the polkit file differently for web server agnostic support (see post)
rm -f %{buildroot}%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
%check
# Nothing to do. No tests exist.
%post common
# Initial installation
if [ $1 -eq 1 ] ; then

View File

@ -78,7 +78,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,policykit-1|pkexec
,rsyslog | system-log-daemon
,zip
,arp-scan
,arp-scan, iproute2
,libcrypt-eksblowfish-perl
,libdata-entropy-perl
,libvncclient1|libvncclient0

View File

@ -306,7 +306,6 @@ bool EventStream::loadEventData(uint64_t event_id) {
last_timestamp = event_data->start_time;
event_data->frame_count ++;
} else {
Debug(1, "EIther no endtime or no duration, frame_count %d, last_id %d", event_data->frame_count, last_id);
delta = std::chrono::duration_cast<Microseconds>((event_data->end_time - last_timestamp)/(event_data->frame_count-last_id));
Debug(1, "Setting delta from endtime %f - %f / %d - %d",
FPSeconds(event_data->end_time.time_since_epoch()).count(),

View File

@ -20,6 +20,7 @@
#include "zm_time.h"
#include <cinttypes>
#include <ctime>
std::string SystemTimePointToString(SystemTimePoint tp) {
time_t tp_sec = std::chrono::system_clock::to_time_t(tp);
@ -51,3 +52,11 @@ std::string TimePointToString(TimePoint tp) {
snprintf(timePtr, timeString.capacity() - (timePtr - timeString.data()), ".%06" PRIi64, static_cast<int64_t>(now_frac.count()));
return timeString;
}
SystemTimePoint StringToSystemTimePoint(const std::string &timestamp) {
std::tm t{};
strptime(timestamp.c_str(), "%Y-%m-%d %H:%M:%S", &t);
time_t time_t_val = mktime(&t);
SystemTimePoint stp = std::chrono::system_clock::from_time_t(time_t_val);
return stp;
}

View File

@ -123,5 +123,6 @@ class TimeSegmentAdder {
std::string SystemTimePointToString(SystemTimePoint tp);
std::string TimePointToString(TimePoint tp);
SystemTimePoint StringToSystemTimePoint(const std::string &stp);
#endif // ZM_TIME_H

View File

@ -99,17 +99,8 @@ class EventsController extends AppController {
}
$settings['conditions'] = array($conditions, $mon_options);
// How many events to return
$this->loadModel('Config');
$limit = $this->Config->find('list', array(
'conditions' => array('Name' => 'ZM_WEB_EVENTS_PER_PAGE'),
'fields' => array('Name', 'Value')
));
$this->Paginator->settings = $settings;
$events = $this->Paginator->paginate('Event');
// For each event, get the frameID which has the largest score
// also add FS path
$events = $this->Event->find('all', $settings);
// For each event, get the frameID which has the largest score also add FS path
foreach ( $events as $key => $value ) {
$EventObj = new ZM\Event($value['Event']);

View File

@ -1,3 +1,12 @@
"use strict";
var LOADING = 1;
var ajax = null;
var wait_for_events_interval = null;
function evaluateLoadTimes() {
if (liveMode != 1 && currentSpeed == 0) return; // don't evaluate when we are not moving as we can do nothing really fast.
@ -45,26 +54,33 @@ function evaluateLoadTimes() {
$j('#fps').text("Display refresh rate is " + (1000 / currentDisplayInterval).toFixed(1) + " per second, avgFrac=" + avgFrac.toFixed(3) + ".");
} // end evaluateLoadTimes()
function findEventByTime(arr, time, debug) {
function findEventByTime(arr, time, debug=false) {
let start = 0;
let end = arr.length-1; // -1 because 0 based indexing
//console.log("looking for "+time+" Start: " + arr[start].StartTimeSecs + ' End: ' + arr[end].EndTimeSecs);
if (debug) {
if ( arr.length ) {
console.log("looking for "+time+" Start: " + arr[start].StartTimeSecs + ' End: ' + arr[end].EndTimeSecs);
} else {
console.log("looking for "+time+" but nothing in arr");
}
}
// Iterate while start not meets end
while ((start <= end) && (arr[start].StartTimeSecs <= time) && (!arr[end].EndTimeSecs || (arr[end].EndTimeSecs >= time))) {
//console.log("looking for "+time+" Start: " + arr[start].StartTimeSecs + ' End: ' + arr[end].EndTimeSecs);
if (debug)
console.log("looking for "+time+" Start: " + arr[start].StartTimeSecs + ' End: ' + arr[end].EndTimeSecs);
// Find the middle index
const middle = Math.floor((start + end)/2);
const zm_event = arr[middle];
// If element is present at mid, return True
//console.log(middle, zm_event, time);
if (debug) console.log(middle, zm_event, time);
if ((zm_event.StartTimeSecs <= time) && (!zm_event.EndTimeSecs || (zm_event.EndTimeSecs >= time))) {
//console.log("Found it at ", zm_event);
if (debug) console.log("Found it at ", zm_event);
return zm_event;
}
//console.log("Didn't find it looking for "+time+" Start: " + zm_event.StartTimeSecs + ' End: ' + zm_event.EndTimeSecs);
if (debug) console.log("Didn't find it looking for "+time+" Start: " + zm_event.StartTimeSecs + ' End: ' + zm_event.EndTimeSecs);
// Else look in left or right half accordingly
if (zm_event.StartTimeSecs < time) {
start = middle + 1;
@ -77,7 +93,7 @@ function findEventByTime(arr, time, debug) {
return false;
} // end function findEventByTime
function findFrameByTime(arr, time, debug) {
function findFrameByTime(arr, time, debug=false) {
if (!arr) {
console.log("No array in findFrameByTime");
return false;
@ -149,7 +165,7 @@ function findFrameByTime(arr, time, debug) {
break;
}
} // end while
if (debug) console.log("Didn't find it");
if (debug) console.log("Didn't find frame it");
return false;
} // end function findFrameByTime(arr, time, debug=false)
@ -180,7 +196,6 @@ function getFrame(monId, time, last_Frame) {
let Event = findEventByTime(events_for_monitor[monId], time, false);
if (Event === false) {
// This might be better with a binary search
for (let i=0, len=events_for_monitor[monId].length; i<len; i++) {
const event_id = events_for_monitor[monId][i].Id;
const e = events[event_id];
@ -207,19 +222,28 @@ function getFrame(monId, time, last_Frame) {
}
}
}
if (!Event) return;
if (!Event) {
console.log('No event found for ' + time + ' ' + secs2inputstr(time) + ' on monitor ' + monId);
return;
}
if (!Event.FramesById) {
// It is assumed at this time that every event has frames
console.log('No FramesById for event ', Event.Id);
load_Frames({event_id: Event}).then(function() {
var event_list = {};
event_list[Event.Id] = Event;
loadFrames(event_list).then(function() {
if (!Event.FramesById) {
console.log("No FramesById after load_Frames!", Event);
console.log("No FramesById after loadFrames!", Event);
}
return findFrameByTime(Event.FramesById, time);
}, function(Error) {
console.log(Error);
});
return;
} else if (!Event.FramesById.length) {
console.log("frames loading for event " + Event.Id);
return;
}
// Need to get frame by time, not some fun calc that assumes frames have the same length.
@ -302,37 +326,32 @@ function getImageSource(monId, time) {
return;
}
let scale = parseInt(100*monitorCanvasObj[monId].width / monitorWidth[monId]);
let scale = parseInt(100 * monitorCanvasObj[monId].width / monitorWidth[monId]);
if (scale > 100) {
scale = 100;
} else {
scale = 10 * parseInt(scale/10);
scale = 10 * parseInt(scale/10); // Round to nearest 10
// May need to limit how small we can go to maintain fidelity
}
// Storage[0] is guaranteed to exist as we make sure it is there in montagereview.js.php
const storage = Storage[e.StorageId] ? Storage[e.StorageId] : Storage[0];
// monitorServerId may be 0, which gives us the default Server entry
const server = storage.ServerId ? Servers[storage.ServerId] : Servers[monitorServerId[monId]];
return server.PathToZMS + '?mode=jpeg&frames=1&event=' + Frame.EventId + '&frame='+frame_id +
return server.PathToZMS + '?mode=jpeg&event=' + Frame.EventId + '&frame='+frame_id +
//"&width=" + monitorCanvasObj[monId].width +
//"&height=" + monitorCanvasObj[monId].height +
"&scale=" + scale +
"&frames=1" +
"&rate=" + 100*speeds[speedIndex] +
'&' + auth_relay;
return server.PathToIndex +
'?view=image&eid=' + Frame.EventId + '&fid='+frame_id +
"&width=" + monitorCanvasObj[monId].width +
"&height=" + monitorCanvasObj[monId].height;
} // end found Frame
return '';
} // end function getImageSource
// callback when loading an image. Will load itself to the canvas, or draw no data
function imagedone( obj, monId, success ) {
if ( success ) {
if (success) {
const canvasCtx = monitorCanvasCtx[monId];
const canvasObj = monitorCanvasObj[monId];
@ -369,42 +388,25 @@ function imagedone( obj, monId, success ) {
return;
}
function loadNoData( monId ) {
if ( monId ) {
var canvasCtx = monitorCanvasCtx[monId];
var canvasObj = monitorCanvasObj[monId];
canvasCtx.fillStyle="white";
canvasCtx.fillRect(0, 0, canvasObj.width, canvasObj.height);
var textSize=canvasObj.width * 0.15;
var text="No Event";
canvasCtx.font = "600 " + textSize.toString() + "px Arial";
canvasCtx.fillStyle="black";
var textWidth = canvasCtx.measureText(text).width;
canvasCtx.fillText(text, canvasObj.width/2 - textWidth/2, canvasObj.height/2);
} else {
console.log("No monId in loadNoData");
}
}
function writeText( monId, text ) {
if ( monId ) {
var canvasCtx = monitorCanvasCtx[monId];
var canvasObj = monitorCanvasObj[monId];
function writeText(monId, text) {
if (monId) {
const canvasCtx = monitorCanvasCtx[monId];
const canvasObj = monitorCanvasObj[monId];
//canvasCtx.fillStyle="white";
//canvasCtx.fillRect(0, 0, canvasObj.width, canvasObj.height);
var textSize=canvasObj.width * 0.15;
canvasCtx.font = "600 " + textSize.toString() + "px Arial";
canvasCtx.fillStyle="white";
var textSize = canvasObj.width * 0.15;
canvasCtx.font = '600 ' + textSize.toString() + "px Arial";
canvasCtx.fillStyle = 'white';
var textWidth = canvasCtx.measureText(text).width;
canvasCtx.fillText(text, canvasObj.width/2 - textWidth/2, canvasObj.height/2);
} else {
console.log("No monId in loadNoData");
console.log('No monId in writeText');
}
}
// Either draws the
function loadImage2Monitor( monId, url ) {
if ( monitorLoading[monId] && monitorImageObject[monId].src != url ) {
function loadImage2Monitor(monId, url) {
if ( monitorLoading[monId] && (monitorImageObject[monId].src != url) ) {
// never queue the same image twice (if it's loading it has to be defined, right?
monitorLoadingStageURL[monId] = url; // we don't care if we are overriting, it means it didn't change fast enough
} else {
@ -430,91 +432,90 @@ function timerFire() {
console.log("Turn off interrupts timerInterfave" + timerInterval);
}
if ( (currentSpeed > 0 || liveMode != 0) && ! timerObj ) {
timerObj = setInterval(timerFire, timerInterval); // don't fire out of live mode if speed is zero
}
if (liveMode) {
outputUpdate(currentTimeSecs); // In live mode we basically do nothing but redisplay
} else if (currentTimeSecs + playSecsPerInterval >= maxTimeSecs) {
// beyond the end just stop
console.log("Current time " + currentTimeSecs + " + " + playSecsPerInterval + " >= " + maxTimeSecs + " so stopping");
if (speedIndex) setSpeed(0);
setSpeed(0);
outputUpdate(currentTimeSecs);
} else {
//console.log("Current time " + currentTimeSecs + " + " + playSecsPerInterval);
} else if (playSecsPerInterval || (currentTimeSecs==minTimeSecs)) {
outputUpdate(playSecsPerInterval + currentTimeSecs);
}
if ((currentSpeed > 0 || liveMode != 0) && !timerObj) {
timerObj = setInterval(timerFire, timerInterval); // don't fire out of live mode if speed is zero
} else {
console.log("CurrentSpeed", currentSpeed, "liveMode", liveMode, timerObj);
}
return;
}
} // end function timerFire()
// val is seconds?
function drawSliderOnGraph(val) {
var sliderWidth=10;
var sliderLineWidth=1;
var sliderHeight=cHeight;
if (numMonitors <= 0) {
return;
}
if ( liveMode == 1 ) {
val = Math.floor( Date.now() / 1000);
}
// Set some sizes
var sliderWidth=10;
var sliderLineWidth=1;
var sliderHeight=cHeight;
// Set some sizes
var labelpx = Math.max( 6, Math.min( 20, parseInt(cHeight * timeLabelsFractOfRow / (numMonitors+1)) ) );
var labbottom = parseInt(cHeight * 0.2 / (numMonitors+1)).toString() + "px"; // This is positioning same as row labels below, but from bottom so 1-position
var labfont = labelpx + "px"; // set this like below row labels
if ( numMonitors > 0 ) {
// if we have no data to display don't do the slider itself
var sliderX = parseInt((val - minTimeSecs) / rangeTimeSecs * cWidth - sliderWidth/2); // position left side of slider
if ( sliderX < 0 ) sliderX = 0;
if ( sliderX + sliderWidth > cWidth ) {
sliderX = cWidth-sliderWidth-1;
}
// if we have no data to display don't do the slider itself
let sliderX = parseInt((val - minTimeSecs) / rangeTimeSecs * cWidth - sliderWidth/2); // position left side of slider
if ( sliderX < 0 ) sliderX = 0;
if ( sliderX + sliderWidth > cWidth ) sliderX = cWidth-sliderWidth-1;
// If we have data already saved first restore it from LAST time
// If we have data already saved first restore it from LAST time
if ( typeof underSlider !== 'undefined' ) {
ctx.putImageData(underSlider, underSliderX, 0, 0, 0, sliderWidth, sliderHeight);
underSlider = undefined;
}
if ( liveMode == 0 ) {
// we get rid of the slider if we switch to live (since it may not be in the "right" place)
// Now save where we are putting it THIS time
underSlider = ctx.getImageData(sliderX, 0, sliderWidth, sliderHeight);
// And add in the slider'
ctx.lineWidth = sliderLineWidth;
ctx.strokeStyle = 'yellow';
// looks like strokes are on the outside (or could be) so shrink it by the line width so we replace all the pixels
ctx.strokeRect(sliderX+sliderLineWidth, sliderLineWidth, sliderWidth - 2*sliderLineWidth, sliderHeight - 2*sliderLineWidth);
underSliderX = sliderX;
}
var o = document.getElementById('scruboutput');
if ( liveMode == 1 ) {
o.innerHTML = "Live Feed @ " + (1000 / currentDisplayInterval).toFixed(1) + " fps";
o.style.color = "red";
} else {
o.innerHTML = secs2dbstr(val);
o.style.color = 'white';
}
o.style.position = "absolute";
o.style.bottom = labbottom;
o.style.font = labfont;
// try to get length and then when we get too close to the right switch to the left
var len = o.offsetWidth;
var x;
if ( sliderX > cWidth/2 ) {
x = sliderX - len - 10;
} else {
x = sliderX + 10;
}
o.style.left = x.toString() + "px";
if ( typeof underSlider !== 'undefined' ) {
ctx.putImageData(underSlider, underSliderX, 0, 0, 0, sliderWidth, sliderHeight);
underSlider = undefined;
}
if ( liveMode == 0 ) {
// we get rid of the slider if we switch to live (since it may not be in the "right" place)
// Now save where we are putting it THIS time
underSlider = ctx.getImageData(sliderX, 0, sliderWidth, sliderHeight);
// And add in the slider'
ctx.lineWidth = sliderLineWidth;
ctx.strokeStyle = 'yellow';
// looks like strokes are on the outside (or could be) so shrink it by the line width so we replace all the pixels
ctx.strokeRect(sliderX+sliderLineWidth, sliderLineWidth, sliderWidth - 2*sliderLineWidth, sliderHeight - 2*sliderLineWidth);
underSliderX = sliderX;
}
var o = document.getElementById('scruboutput');
if ( liveMode == 1 ) {
o.innerHTML = 'Live Feed @ ' + (1000 / currentDisplayInterval).toFixed(1) + ' fps';
o.style.color = 'red';
} else {
o.innerHTML = secs2dbstr(val);
o.style.color = 'white';
}
o.style.position = 'absolute';
o.style.bottom = labbottom;
o.style.font = labfont;
// try to get length and then when we get too close to the right switch to the left
var len = o.offsetWidth;
var x;
if ( sliderX > cWidth/2 ) {
x = sliderX - len - 10;
} else {
x = sliderX + 10;
}
o.style.left = x.toString() + "px";
// This displays (or not) the left/right limits depending on how close the slider is.
// Because these change widths if the slider is too close, use the slider width as an estimate for the left/right label length (i.e. don't recalculate len from above)
// If this starts to collide increase some of the extra space
var o = document.getElementById('scrubleft');
o = document.getElementById('scrubleft');
o.innerHTML = secs2dbstr(minTimeSecs);
o.style.position = "absolute";
o.style.bottom = labbottom;
@ -532,7 +533,7 @@ function drawSliderOnGraph(val) {
o.style.display = "inline-flex"; // safari won't take this but will just ignore
}
var o = document.getElementById('scrubright');
o = document.getElementById('scrubright');
o.innerHTML = secs2dbstr(maxTimeSecs);
o.style.position = "absolute";
o.style.bottom = labbottom;
@ -547,6 +548,40 @@ function drawSliderOnGraph(val) {
}
}
function drawFrameOnGraph(frame) {
if (!frame.Score) return;
// Now put in scored frames (if any)
var x1 = parseInt( (frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth); // round low end down
var x2 = parseInt( (frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ); // round up
if (x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
ctx.fillStyle=monitorColour[Event.MonitorId];
//ctx.fillStyle = '#ff0000';
ctx.globalAlpha = 0.4 + 0.6 * (1 - frame.Score/maxScore); // Background is scaled but even lowest is twice as dark as the background
const MonitorId = events[frame.EventId].MonitorId;
ctx.fillRect(x1, monitorIndex[MonitorId]*rowHeight, x2-x1, rowHeight-2);
//console.log("Drew frame from ", x1, MonitorId, monitorIndex[MonitorId]*rowHeight, x2-x1, rowHeight);
}
function drawEventOnGraph(Event) {
// round low end down
const x1 = parseInt((Event.StartTimeSecs - minTimeSecs) / rangeTimeSecs * cWidth);
if (!Event.EndTimeSecs) Event.EndTimeSecs = maxTimeSecs;
// round high end up to be sure consecutive ones connect
const x2 = parseInt((Event.EndTimeSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 );
if (!monitorColour[Event.MonitorId]) {
console.log("No colour for ", Event.MonitorId, monitorColour);
ctx.fillStyle = '#43bcf2';
} else {
ctx.fillStyle = monitorColour[Event.MonitorId];
}
ctx.globalAlpha = 0.2; // light color for background
// Erase any overlap so it doesn't look artificially darker
ctx.clearRect(x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight);
ctx.fillRect(x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight-2);
//outputUpdate(currentTimeSecs);
console.log("Drew event from ", x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight);
}
function drawGraph() {
var divWidth = document.getElementById('timelinediv').clientWidth;
canvas.width = cWidth = divWidth; // Let it float and determine width (it should be sized a bit smaller percentage of window)
@ -567,38 +602,24 @@ function drawGraph() {
underSlider = undefined;
return;
}
var rowHeight = parseInt(cHeight / (numMonitors + 1) ); // Leave room for a scale of some sort
rowHeight = parseInt(cHeight / (numMonitors + 1) ); // Leave room for a scale of some sort
// first fill in the bars for the events (not alarms)
for ( var event_id in events ) {
var Event = events[event_id];
// round low end down
var x1 = parseInt((Event.StartTimeSecs - minTimeSecs) / rangeTimeSecs * cWidth);
var x2 = parseInt((Event.EndTimeSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ); // round high end up to be sure consecutive ones connect
ctx.fillStyle = monitorColour[Event.MonitorId];
ctx.globalAlpha = 0.2; // light color for background
ctx.clearRect(x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight); // Erase any overlap so it doesn't look artificially darker
ctx.fillRect(x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight);
for ( var frame_id in Event.FramesById ) {
var Frame = Event.FramesById[frame_id];
if ( ! Frame.Score ) {
continue;
}
// Now put in scored frames (if any)
var x1=parseInt( (Frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth); // round low end down
var x2=parseInt( (Frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ); // round up
if (x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
//ctx.fillStyle=monitorColour[Event.MonitorId];
ctx.globalAlpha = 0.4 + 0.6 * (1 - Frame.Score/maxScore); // Background is scaled but even lowest is twice as dark as the background
ctx.fillRect(x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight);
} // end foreach frame
console.log(events);
for (const event_id in events) {
const Event = events[event_id];
drawEventOnGraph(Event);
if (Event.FramesById) {
for (const frame_id in Event.FramesById ) {
const Frame = Event.FramesById[frame_id];
if (!Frame.Score) continue;
drawFrameOnGraph(Frame);
} // end foreach frame
}
} // end foreach Event
for ( var i=0; i < numMonitors; i++ ) {
for (let i=0; i < numMonitors; i++) {
// Note that this may be a sparse array
ctx.font = parseInt(rowHeight * timeLabelsFractOfRow).toString() + "px Georgia";
ctx.fillStyle = "white";
@ -624,7 +645,7 @@ function redrawScreen() {
var scaleDiv = $j('#ScaleDiv');
var fit = $j('#fit');
if ( liveMode == 1 ) {
if (liveMode == 1) {
// if we are not in live view switch to history -- this has to come before fit in case we re-establish the timeline
dateTimeDiv.hide();
speedDiv.hide();
@ -646,12 +667,11 @@ function redrawScreen() {
panLeft.show();
panRight.show();
downloadVideo.show();
drawGraph();
}
var monitors = $j('#monitors');
if ( fitMode == 1 ) {
if (fitMode == 1) {
var fps = $j('#fps');
var vh = window.innerHeight;
var mh = (vh - monitors.position().top - fps.outerHeight());
@ -680,13 +700,14 @@ function redrawScreen() {
} // end function redrawScreen
function outputUpdate(time) {
drawSliderOnGraph(time);
for ( var i=0; i < numMonitors; i++ ) {
var src = getImageSource(monitorPtr[i], time);
//console.log("New image src: " + src);
loadImage2Monitor(monitorPtr[i], src);
if (Object.keys(events).length !== 0) {
for ( let i=0; i < numMonitors; i++ ) {
const src = getImageSource(monitorPtr[i], time);
loadImage2Monitor(monitorPtr[i], src);
}
}
currentTimeSecs = time;
drawSliderOnGraph(time);
}
// Found this here: http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
@ -728,8 +749,9 @@ function tmove(event) {
function mmove(event) {
if ( mouseisdown ) {
// only do anything if the mouse is depressed while on the sheet
var sec = Math.floor(minTimeSecs + rangeTimeSecs / event.target.width * event.target.relMouseCoords(event).x);
outputUpdate(sec);
const relx = event.target.relMouseCoords(event).x;
const sec = Math.floor(minTimeSecs + rangeTimeSecs / event.target.width * relx);
if (parseInt(sec)) outputUpdate(sec);
}
}
@ -748,7 +770,7 @@ function secs2inputstr(s) {
}
function secs2dbstr(s) {
if ( ! parseInt(s) ) {
if (!parseInt(s)) {
console.log("Invalid value for " + s + " seconds");
return '';
}
@ -761,7 +783,7 @@ function secs2dbstr(s) {
}
function setFit(value) {
fitMode=value;
fitMode = value;
redrawScreen();
}
@ -896,6 +918,7 @@ function click_zoomout() {
function click_panleft() {
minTimeSecs = parseInt(minTimeSecs - rangeTimeSecs/2);
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
currentTimeSecs -= rangeTimeSecs/2;
clicknav(minTimeSecs, maxTimeSecs, 0);
}
function click_panright() {
@ -941,7 +964,7 @@ function allnon() {
clicknav(0, 0, 0);
}
// >>>>>>>>>>>>>>>> Handles individual monitor clicks and navigation to the standard event/watch display
// Handles individual monitor clicks and navigation to the standard event/watch display
function showOneMonitor(monId, event) {
// link out to the normal view of one event's data
@ -1019,19 +1042,134 @@ function changeDateTime(e) {
// Reloading can take a while, so stop interrupts to reduce load
clearInterval(timerObj);
timerObj = null;
const form = $j('#montagereview_form');
console.log(form.serialize());
var uri = "?" + form.serialize() + zoomStr + "&scale=" + $j("#scaleslider")[0].value + "&speed=" + speeds[$j("#speedslider")[0].value];
drawGraph();
console.log("ChangeDateTime");
loadEventData();
console.log("timerFire from changeDateTime");
wait_for_events();
//const form = $j('#montagereview_form');
//console.log(form.serialize());
//var uri = "?" + form.serialize() + zoomStr + "&scale=" + $j("#scaleslider")[0].value + "&speed=" + speeds[$j("#speedslider")[0].value];
//var uri = "?view=" + currentView + fitStr + minStr + maxStr + liveStr + zoomStr + "&scale=" + $j("#scaleslider")[0].value + "&speed=" + speeds[$j("#speedslider")[0].value];
window.location = uri;
//window.location = uri;
}
// >>>>>>>>> Initialization that runs on window load by being at the bottom
function loadEventData(e) {
LOADING = true;
var monitors = monitorData;
var data = {};
var mon_ids = [];
for (let monitor_i=0, monitors_len=monitors.length; monitor_i < monitors_len; monitor_i++) {
const monitor = monitors[monitor_i];
monitorLoading[monitor.Id] = false;
mon_ids[mon_ids.length] = monitor.Id;
}
var url = Servers[serverId].urlToApi()+'/events/index';
$j('#fieldsTable input,#fieldsTable select').each(function(index) {
const el = $j(this);
const val = el.val();
if (val && (!Array.isArray(val) || val.length)) {
const name = el.attr('name');
if (name) {
const found = name.match(/filter\[Query\]\[terms\]\[(\d)+\]\[val\]/);
if (found) {
const attr_name = 'filter[Query][terms]['+found[1]+'][attr]';
const attr = this.form.elements[attr_name];
const op_name = 'filter[Query][terms]['+found[1]+'][op]';
const op = this.form.elements[op_name];
if (attr) {
url += '/'+attr.value+' '+op.value+':'+encodeURIComponent(val);
} else {
console.log('No attr for '+attr_name);
}
//} else {
//console.log("No match for " + name);
}
data[name] = val;
const cookie = el.attr('data-cookie');
if (cookie) setCookie(cookie, val, 3600);
} // end if name
} // end if val
});
function receive_events(data) {
if (data.result == 'Error') {
alert(data.message);
return;
}
if (!data.events) {
console.log(data);
return;
}
console.log("Event data ", data);
if (data.events.length) {
const event_list = {};
for (let i=0, len = data.events.length; i<len; i++) {
const ev = data.events[i].Event;
events[parseInt(ev.Id)] = ev;
if (!events_by_monitor_id[ev.MonitorId]) {
events_by_monitor_id[ev.MonitorId] = []; // just event ids
events_for_monitor[ev.MonitorId] = []; // id=>event
}
events_by_monitor_id[ev.MonitorId].push(ev.Id);
events_for_monitor[ev.MonitorId].push(ev);
drawEventOnGraph(ev);
event_list[ev.Id] = ev;
events[ev.id] = ev;
}
loadFrames(event_list);
}
} // end function receive_events
if (ajax) ajax.abort();
LOADING = false;
if (mon_ids.length) {
for (let i=0; i < mon_ids.length; i++) {
ajax = $j.ajax({
url: url+ '/MonitorId:'+mon_ids[i]+ '.json'+'?'+auth_relay,
method: 'GET',
//url: thisUrl + '?view=request&request=events&task=query&sort=Id&order=ASC',
//data: data,
timeout: 0,
success: receive_events,
error: function(jqXHR) {
ajax = null;
console.log("error", jqXHR);
//logAjaxFail(jqXHR);
//$j('#eventTable').bootstrapTable('refresh');
}
});
} // end foreach monitor
} else {
ajax = $j.ajax({
url: url+'.json'+'?'+auth_relay,
method: 'GET',
//url: thisUrl + '?view=request&request=events&task=query&sort=Id&order=ASC',
//data: data,
timeout: 0,
success: receive_events,
error: function(jqXHR) {
ajax = null;
console.log("error", jqXHR);
//logAjaxFail(jqXHR);
//$j('#eventTable').bootstrapTable('refresh');
}
});
}
return;
} // end function loadEventData
function initPage() {
if (!liveMode) {
load_Frames(events);
canvas = document.getElementById('timeline');
canvas.addEventListener('mousemove', mmove, false);
@ -1041,59 +1179,38 @@ function initPage() {
canvas.addEventListener('mouseout', mout, false);
ctx = canvas.getContext('2d', {willReadFrequently: true});
// draw an empty timeline
drawGraph();
}
for ( let i = 0, len = monitorPtr.length; i < len; i += 1 ) {
for (let i = 0, len = monitorPtr.length; i < len; i += 1) {
const monId = monitorPtr[i];
if (!monId) continue;
monitorCanvasObj[monId] = document.getElementById('Monitor'+monId);
if ( !monitorCanvasObj[monId] ) {
alert("Couldn't find DOM element for Monitor" + monId + "monitorPtr.length=" + len);
} else {
monitorCanvasCtx[monId] = monitorCanvasObj[monId].getContext('2d');
const imageObject = monitorImageObject[monId] = new Image();
imageObject.monId = monId;
imageObject.onload = function() {
imagedone(this, this.monId, true);
};
imageObject.onerror = function() {
imagedone(this, this.monId, false);
};
loadImage2Monitor(monId, monitorImageURL[monId]);
monitorCanvasObj[monId].addEventListener('click', clickMonitor, false);
continue;
}
monitorCanvasCtx[monId] = monitorCanvasObj[monId].getContext('2d');
const imageObject = monitorImageObject[monId] = new Image();
imageObject.monId = monId;
imageObject.onload = function() {
imagedone(this, this.monId, true);
};
imageObject.onerror = function() {
imagedone(this, this.monId, false);
};
if (liveMode) loadImage2Monitor(monId, monitorImageURL[monId]);
monitorCanvasObj[monId].addEventListener('click', clickMonitor, false);
} // end foreach monitor
setSpeed(speedIndex);
//setFit(fitMode); // will redraw
//setLive(liveMode); // will redraw
loadEventData();
redrawScreen();
/*
$j('#minTime').datetimepicker({
timeFormat: "HH:mm:ss",
dateFormat: "yy-mm-dd",
maxDate: +0,
constrainInput: false,
onClose: function(newDate, oldData) {
if (newDate !== oldData.lastVal) {
changeDateTime();
}
}
});
$j('#maxTime').datetimepicker({
timeFormat: "HH:mm:ss",
dateFormat: "yy-mm-dd",
minDate: minTime,
maxDate: +0,
constrainInput: false,
onClose: function(newDate, oldData) {
if ( newDate !== oldData.lastVal ) {
changeDateTime();
}
}
});
*/
$j('#scaleslider').bind('change', function() {
setScale(this.value);
});
@ -1126,6 +1243,19 @@ function initPage() {
el.on('change', changeDateTime);
}
});
wait_for_events();
}
function wait_for_events() {
if (Object.keys(events).length === 0) {
if (!wait_for_events_interval)
wait_for_events_interval = setInterval(wait_for_events, 1000);
} else {
clearInterval(wait_for_events_interval);
wait_for_events_interval = null;
timerFire();
}
}
function takeSnapshot() {
@ -1156,7 +1286,7 @@ window.addEventListener("resize", redrawScreen, {passive: true});
window.addEventListener('DOMContentLoaded', initPage);
/* Expects and Object, not an array, of EventId=>Event mappings. */
function load_Frames(zm_events) {
function loadFrames(zm_events) {
console.log("Loading frames", zm_events);
return new Promise(function(resolve, reject) {
const url = Servers[serverId].urlToApi()+'/frames/index';
@ -1219,4 +1349,4 @@ function load_Frames(zm_events) {
} // end while zm_events.legtnh
} // end Promise
);
} // end function load_Frames(Event)
} // end function loadFrames(Event)

View File

@ -31,6 +31,7 @@ var fitMode=<?php echo $fitMode?>;
// slider scale, which is only for replay and relative to real time
var currentSpeed=<?php echo $speeds[$speedIndex]?>;
var speedIndex=<?php echo $speedIndex?>;
var lastSpeedIndex=0;
// will be set based on performance, this is the display interval in milliseconds
// for history, and fps for live, and dynamically determined (in ms)
@ -58,6 +59,7 @@ if (!$liveMode) {
echo "const events = {\n";
$EventsById = array();
if (0) {
$result = dbQuery($eventsSql);
if ($result) {
while ( $event = $result->fetch(PDO::FETCH_ASSOC) ) {
@ -67,14 +69,15 @@ if (!$liveMode) {
$events_by_monitor_id = array();
$eventMaxSecs = 0;
foreach ($EventsById as $event_id=>$event) {
$StartTimeSecs = $event['StartTimeSecs'];
$EndTimeSecs = $event['EndTimeSecs'];
# It isn't neccessary to do this for each event. We should be able to just look at the first and last
if ( !$minTimeSecs or $minTimeSecs > $StartTimeSecs ) $minTimeSecs = $StartTimeSecs;
if ( !$maxTimeSecs or $maxTimeSecs < $EndTimeSecs ) $maxTimeSecs = $EndTimeSecs;
if ($StartTimeSecs > $eventMaxSecs) $eventMaxSecs = $StartTimeSecs;
$event_json = json_encode($event, JSON_PRETTY_PRINT|JSON_NUMERIC_CHECK);
echo " $event_id : $event_json,\n";
@ -89,10 +92,12 @@ if (!$liveMode) {
$events_by_monitor_id[$event['MonitorId']] = array();
array_push($events_by_monitor_id[$event['MonitorId']], $event_id);
} # end foreach Event
if ($eventMaxSecs < $maxTimeSecs) $maxTimeSecs = $eventMaxSecs;
}
echo ' };
const events_for_monitor = [];
const events_by_monitor_id = '.json_encode($events_by_monitor_id, JSON_NUMERIC_CHECK).PHP_EOL;
const events_for_monitor = {};
const events_by_monitor_id = {};'; #.json_encode($events_by_monitor_id, JSON_NUMERIC_CHECK).PHP_EOL;
// if there is no data set the min/max to the passed in values
if ( $index == 0 ) {
@ -129,6 +134,28 @@ if ( !$have_storage_zero ) {
$Storage = new ZM\Storage();
echo 'Storage[0] = ' . $Storage->to_json(). ";\n";
}
echo "\nconst monitorData = [];\n";
foreach ( $monitors as $monitor ) {
if ($monitor->Deleted() or !$monitor->canView()) continue;
?>
monitorData[monitorData.length] = {
'Id': <?php echo $monitor->Id() ?>,
'Name': '<?php echo $monitor->Name() ?>',
'connKey': '<?php echo $monitor->connKey() ?>',
'Width': <?php echo $monitor->ViewWidth() ?>,
'Height':<?php echo $monitor->ViewHeight() ?>,
'JanusEnabled':<?php echo $monitor->JanusEnabled() ?>,
'Url': '<?php echo $monitor->UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'UrlToZms': '<?php echo $monitor->UrlToZMS( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
'onclick': function(){window.location.assign( '?view=watch&mid=<?php echo $monitor->Id() ?>' );},
'Type': '<?php echo $monitor->Type() ?>',
'Refresh': '<?php echo $monitor->Refresh() ?>',
'Janus_Pin': '<?php echo $monitor->Janus_Pin() ?>',
'WebColour': '<?php echo $monitor->WebColour() ?>'
};
<?php
} // end foreach monitor
echo '
var monitorName = [];
@ -183,16 +210,17 @@ foreach ( $monitors as $m ) {
}
echo "
var numMonitors = $numMonitors;
var minTimeSecs=parseInt($minTimeSecs);
var maxTimeSecs=parseInt($maxTimeSecs);
var minTimeSecs =parseInt($minTimeSecs);
var maxTimeSecs =parseInt($maxTimeSecs);
var minTime='$minTime';
var maxTime='$maxTime';
";
echo 'var rangeTimeSecs='.($maxTimeSecs - $minTimeSecs + 1).";\n";
if ( isset($defaultCurrentTimeSecs) )
if ( isset($defaultCurrentTimeSecs) ) {
echo 'var currentTimeSecs=parseInt('.$defaultCurrentTimeSecs.");\n";
else
} else {
echo 'var currentTimeSecs=parseInt('.$minTimeSecs.");\n";
}
echo 'var speeds=[';
for ( $i=0; $i < count($speeds); $i++ )
@ -202,6 +230,7 @@ echo "];\n";
var cWidth; // save canvas width
var cHeight; // save canvas height
var rowHeight = 0;
var canvas; // global canvas definition so we don't have to keep looking it up
var ctx = null;
var underSlider; // use this to hold what is hidden by the slider