diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 40b45f1cf..2865f0936 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -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 diff --git a/distros/ubuntu2004/control b/distros/ubuntu2004/control index 12eb558f8..43d75ac28 100644 --- a/distros/ubuntu2004/control +++ b/distros/ubuntu2004/control @@ -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 diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 787d9181d..80b8dc440 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -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((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(), diff --git a/src/zm_time.cpp b/src/zm_time.cpp index fd8c69d67..a962f34e4 100644 --- a/src/zm_time.cpp +++ b/src/zm_time.cpp @@ -20,6 +20,7 @@ #include "zm_time.h" #include +#include 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(now_frac.count())); return timeString; } + +SystemTimePoint StringToSystemTimePoint(const std::string ×tamp) { + 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; +} diff --git a/src/zm_time.h b/src/zm_time.h index 0ee10ab1c..eec8a0987 100644 --- a/src/zm_time.h +++ b/src/zm_time.h @@ -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 diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index 9a1181253..935c7d6d8 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -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']); diff --git a/web/skins/classic/views/js/montagereview.js b/web/skins/classic/views/js/montagereview.js index 7c7641086..805c044a0 100644 --- a/web/skins/classic/views/js/montagereview.js +++ b/web/skins/classic/views/js/montagereview.js @@ -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 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; ievent + } + 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) diff --git a/web/skins/classic/views/js/montagereview.js.php b/web/skins/classic/views/js/montagereview.js.php index d96330ed0..fb827874e 100644 --- a/web/skins/classic/views/js/montagereview.js.php +++ b/web/skins/classic/views/js/montagereview.js.php @@ -31,6 +31,7 @@ var fitMode=; // slider scale, which is only for replay and relative to real time var currentSpeed=; var 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': Id() ?>, + 'Name': 'Name() ?>', + 'connKey': 'connKey() ?>', + 'Width': ViewWidth() ?>, + 'Height':ViewHeight() ?>, + 'JanusEnabled':JanusEnabled() ?>, + 'Url': 'UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>', + 'UrlToZms': 'UrlToZMS( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>', + 'onclick': function(){window.location.assign( '?view=watch&mid=Id() ?>' );}, + 'Type': 'Type() ?>', + 'Refresh': 'Refresh() ?>', + 'Janus_Pin': 'Janus_Pin() ?>', + 'WebColour': 'WebColour() ?>' +}; +