Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Viewing recorded events on Montage page #4140

Open
wants to merge 59 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
368f4e8
Create vis-timeline-graph2d.min.css
IgorA100 Sep 18, 2024
0c497b1
Create vis-timeline-graph2d.min.js
IgorA100 Sep 18, 2024
0345f6c
Create montage_class.php
IgorA100 Sep 18, 2024
f41d3a9
Added automatic loading of classes for any Views modes (skin.php)
IgorA100 Sep 18, 2024
2d08ed4
Added file upload for TimeLine (functions.php)
IgorA100 Sep 18, 2024
49a5ed0
Create montage.php
IgorA100 Sep 18, 2024
36b97a3
Update MonitorStream.js
IgorA100 Sep 18, 2024
e2fd93c
Update montage.js.php
IgorA100 Sep 18, 2024
62007dd
Added "Allow setting of layout in request" (montage_class.php)
IgorA100 Sep 19, 2024
b1b1752
Moved part of the code to "montage_class.php" (montage.php)
IgorA100 Sep 19, 2024
cb80946
Merge branch 'ZoneMinder:master' into patch-474778
IgorA100 Sep 19, 2024
09b76b1
Added "streamObj.responseJSON.result" handling, started handling doub…
IgorA100 Sep 19, 2024
e16f33e
Style for placing the hide filter button on #mfbpanel (skin.css)
IgorA100 Sep 19, 2024
d832c4d
Added style for buttons "btn-view-event, btn-fullscreen" located on t…
IgorA100 Sep 19, 2024
53b7208
Removed accidentally unnecessary code (skin.css)
IgorA100 Sep 19, 2024
51367f7
Added styles for TimeLine (montage.css)
IgorA100 Sep 19, 2024
0e3b715
Global changes (montage.js)
IgorA100 Sep 20, 2024
d538ede
Merge branch 'master' into patch-474778
IgorA100 Sep 20, 2024
705115c
Added handling relative SRC for URL() (montage.js)
IgorA100 Sep 20, 2024
873d715
#timelinediv left and right alignment at button level (montage.css)
IgorA100 Sep 20, 2024
03a09b5
Merge branch 'ZoneMinder:master' into patch-474778
IgorA100 Sep 22, 2024
2febf65
Merge branch 'master' into patch-474778
IgorA100 Sep 23, 2024
2156768
Fix: Defining baseURL (montage.js)
IgorA100 Sep 25, 2024
2a90d2b
on_scroll() should only be used for live viewing for now. (montage.js)
IgorA100 Sep 25, 2024
d85c854
Added processing of the "path" parameter in the query string (image.php)
IgorA100 Sep 27, 2024
1997b53
If there is no last frame file (or it is corrupted) - display a place…
IgorA100 Sep 27, 2024
09a2c17
Merge branch 'ZoneMinder:master' into patch-474778
IgorA100 Sep 27, 2024
d85be89
Merge branch 'ZoneMinder:master' into patch-474778
IgorA100 Sep 27, 2024
88d79a7
Added a placeholder image "missing frame"
IgorA100 Sep 28, 2024
1054d74
Merge branch 'ZoneMinder:master' into patch-474778
IgorA100 Sep 28, 2024
0dc92b7
Merge branch 'master' into patch-474778
IgorA100 Oct 1, 2024
5afaf13
Merge branch 'ZoneMinder:master' into patch-474778
IgorA100 Oct 1, 2024
6238920
Code optimization (montage.js)
IgorA100 Oct 2, 2024
a8bc0bf
if ($mode == 'live') => if ($mode == 'Live') (montage.php)
IgorA100 Oct 2, 2024
2642cb3
Fix space (montage_class.php)
IgorA100 Oct 2, 2024
8e496dc
Merge branch 'ZoneMinder:master' into patch-474778
IgorA100 Oct 2, 2024
1cf51b9
Removed the use of the "delay" constant as it is no longer used when …
IgorA100 Oct 2, 2024
5e86826
Removed the "delay" argument from the "start" function because it is …
IgorA100 Oct 2, 2024
4a716d8
Merge branch 'master' into patch-474778
IgorA100 Oct 2, 2024
abf503c
Fix: Support sql_mode=only_full_group_by & Frame presence check (mont…
IgorA100 Oct 3, 2024
b91e404
Update montage_class.php
IgorA100 Oct 3, 2024
d3d5c10
Fix: Removed unnecessary $Event definition and its presence check (mo…
IgorA100 Oct 3, 2024
52d3eba
Fix: Removed extra AJAX requests when receiving the next event for pl…
IgorA100 Oct 3, 2024
59756eb
Optimization of SQL query for getting last event (montage_class.php)
IgorA100 Oct 4, 2024
3b10ec1
Fix: Get next event to play (montage.js)
IgorA100 Oct 4, 2024
a83c598
When the event is not playing, display "No recording for this time" i…
IgorA100 Oct 4, 2024
31af773
Fix: save "speed" value in cookies for viewing in recording (montage.js)
IgorA100 Oct 6, 2024
a1753f3
Fix: $defaultSpeed = 1.0 (montage.php)
IgorA100 Oct 6, 2024
b59a72b
On TimeLine display the last, unfinished event (montage_class.php)
IgorA100 Oct 8, 2024
0cb5c61
Fix: Last event that has not yet ended (montage_class.php)
IgorA100 Oct 10, 2024
d224128
Fix last event that has not yet ended (montage_class.php)
IgorA100 Oct 10, 2024
3f8e2a5
Fix: Text size in Canvas (montage.js)
IgorA100 Oct 18, 2024
6ba0f63
Fix: Last unfinished event (display on Timeline, playback) (montage_c…
IgorA100 Oct 18, 2024
f265497
Code cleaning (montage_class.php)
IgorA100 Oct 18, 2024
60363e5
Merge branch 'ZoneMinder:master' into patch-474778
IgorA100 Oct 21, 2024
81568a7
Merge branch 'ZoneMinder:master' into patch-474778
IgorA100 Oct 22, 2024
140d51e
Sanitize input data (montage.php)
IgorA100 Oct 22, 2024
bc351c5
Update montage.js
IgorA100 Oct 22, 2024
61d07af
No generate scaled jpeg if we passed $_REQUEST['path'] (image.php)
IgorA100 Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 212 additions & 0 deletions web/ajax/montage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
<?php
// Загрузка или Live просмотр или в записи

$message = '';
$data = array();

//
// INITIALIZE AND CHECK SANITY
//
if (!canView('Stream')) $message = 'Insufficient permissions for user '.$user->Username();

// Mode
if ( empty($_REQUEST['montage_mode']) ) {
$message = 'Must specify a mode (Live or In recording)';
} else {
$mode = validStr($_REQUEST['montage_mode']);
}

// Action
if ( empty($_REQUEST['montage_action']) ) {
$message = 'Must specify a action.';
} else {
$action = validStr($_REQUEST['montage_action']);
}

$startDateTime = null;
if ( !empty($_REQUEST['StartDateTime']) ) {
$startDateTime = validStr($_REQUEST['StartDateTime']); //Date
}

$endDateTime = null;
if ( !empty($_REQUEST['EndDateTime']) ) {
$endDateTime = validStr($_REQUEST['EndDateTime']); //Date
}

$resolution = 0;
if ( !empty($_REQUEST['Resolution']) ) {
$resolution = validInt($_REQUEST['Resolution']); //The number of seconds between two adjacent pixels of the displayed Timeline
}

$maxFPS = null;
if ( !empty($_REQUEST['maxFPS']) ) {
$maxFPS = validStr($_REQUEST['maxFPS']);
}

$monitorsId = [];
if ( !empty($_REQUEST['MonitorsId']) ) {
$monitorsId = $_REQUEST['MonitorsId'];
}

$dateTime = '';
if ( !empty($_REQUEST['dateTime']) ) {
$dateTime = validStr($_REQUEST['dateTime']);
}

if ($message) {
ZM\Error($message);
ajaxError($message);
return;
}

require_once('includes/Filter.php');
require_once(getSkinFile('views/class/montage_class.php'));

global $basename;
$basename = "montage";

$Montage = new Skin\Montage();
$resultMonitorFilters = $Montage::$resultMonitorFilters;
//$filterbar = $resultMonitorFilters['html'];
$displayMonitors = $resultMonitorFilters['displayMonitors'];

$result = '';

if ($mode == 'Live') {
if ($action == 'filters') {
$result = &$resultMonitorFilters;
} else if ($action == 'grid') {
//$result['monitors'] = &$buildGridMonitorsLive();
$result = $Montage::buildGridMonitorsLive();
}
} else if ($mode == 'inRecording') {
if ($action == 'filters') {
$filter = $Montage::buildGlobalFilters ();
$resultMonitorFilters += ['filter'=>$filter->simple_widget()];
$result = &$resultMonitorFilters;
} else if ($action == 'grid') {
//$result['monitors'] = &buildGridMonitorsInRecords();
$result = $Montage::buildGridMonitorsInRecords($dateTime);
} else if ($action == 'queryEventsForTimeline') {
require_once('includes/Filter.php');
//$filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter();
$filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : '';
$result = $Montage::queryEvents($filter, $monitorsId, $startDateTime, $endDateTime, $resolution, $action, $actionRange = 'range', $maxFPS);
} else if ($action == 'queryEventsForMonitor') {
require_once('includes/Filter.php');
//$filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter();
$filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : '';
$result = $Montage::queryEvents($filter, $monitorsId, $startDateTime, $endDateTime, $resolution, $action, $actionRange = 'range', $maxFPS);
} else if ($action == 'queryNextEventForMonitor') {
require_once('includes/Filter.php');
//$filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter();
$filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : '';
$result = $Montage::queryEvents($filter, $monitorsId, $startDateTime, $endDateTime, $resolution, $action, $actionRange = 'next', $maxFPS);
} else if ($action == 'getMinData') {
require_once('includes/Filter.php');
//$filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter();
$filter = isset($_REQUEST['filter']) ? $_REQUEST['filter'] : '';
$result = $Montage::queryMinData($filter, $monitorsId);
}
} else if ($mode == 'queryEvents') {
//Часть кода перенесена из \usr\share\zoneminder\www\ajax\events.php
// //Строка запроса примерно такая: http://XXX/zm/index.php?view=events&page=1&filter[Query][terms][0][attr]=Monitor&filter[Query][terms][0][op]=%3D&filter[Query][terms][0][val]=5&filter[Query][sort_asc]=1&filter[Query][sort_field]=StartDateTime&filter[Query][skip_locked]=&filter[Query][limit]=0
// //В JS файл добавить типа function filterEvents() { из "skins\classic\views\js\events.js"
// $result = $Montage::queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit);
}
ajaxResponse($result);

/*
// Mid specifies the monitor id we are searching on
if ( empty($_REQUEST['mid']) ) {
$message = 'Must specify a monitor Id';
} else {
$mid = validCardinal($_REQUEST['mid']);
}

// Limit specifies the number of rows to return
if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) {
$message = 'Invalid value for limit: '.$_REQUEST['limit'];
} else {
$limit = $_REQUEST['limit'];
}

if ($message) {
ZM\Error($message);
ajaxError($message);
return;
}

// Sort specifies the name of the column to sort on
$sort = 'Id';
if (isset($_REQUEST['sort'])) {
$sort = $_REQUEST['sort'];
}

// Order specifies the sort direction, either asc or desc
$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC';

//
// MAIN LOOP
//

function getEvents ($mid, $limit, $sort) {
// The names of the dB columns in the events table we are interested in
$columns = array('Id', 'MonitorId', 'StorageId', 'Name', 'Cause', 'StartDateTime', 'EndDateTime', 'Length', 'Frames', 'AlarmFrames', 'TotScore', 'AvgScore', 'MaxScore', 'Archived', 'Emailed', 'Notes', 'DiskSpace');

if ( $sort != 'Id' ) {
if (!in_array($sort, $columns)) {
ZM\Error('Invalid sort field: ' . $sort);
$sort = 'Id';
} else if ($sort == 'EndDateTime') {
if ($order == 'ASC') {
$sort = 'E.EndDateTime IS NULL, E.EndDateTime';
} else {
$sort = 'E.EndDateTime IS NOT NULL, E.EndDateTime';
}
} else {
$sort = 'E.'.$sort;
}
}
$where = 'WHERE MonitorId = '.$mid;

$col_str = ' E.*, GROUP_CONCAT(T.Name SEPARATOR ", ") AS Tags ';

$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E
LEFT JOIN Events_Tags AS ET ON E.Id = ET.EventId
LEFT JOIN Tags AS T ON T.Id = ET.TagId
'.$where.' GROUP BY E.Id ORDER BY '.$sort.' '.$order.' LIMIT ?';

$rows = dbQuery($sql, array($limit));
$returned_rows = array();

if ($rows) {
foreach ( $rows as $row ) {
$event = new ZM\Event($row);
$event->remove_from_cache();
if (!$event->canView()) continue;
if ($event->Monitor()->Deleted()) continue;

$scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width());
$imgSrc = $event->getThumbnailSrc(array(), '&amp;');
$streamSrc = $event->getStreamSrc(array(
'mode'=>'jpeg', 'scale'=>$scale, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>'single', 'rate'=>'400'), '&amp;');

// Modify the row data as needed
$row['imgHtml'] = '<img id="thumbnail' .$event->Id(). '" src="' .$imgSrc. '" alt="Event '.$event->Id().'" width="' .validInt($event->ThumbnailWidth()). '" height="' .validInt($event->ThumbnailHeight()).'" stream_src="' .$streamSrc. '" still_src="' .$imgSrc. '" loading="lazy" />';
$row['Name'] = validHtmlStr($row['Name']);
$row['Length'] = gmdate('H:i:s', intval($row['Length']));

$returned_rows[] = $row;
} # end foreach row matching search
}

$data['rows'] = &$returned_rows;
return $data;
}


ajaxResponse(getEvents ($mid, $limit, $sort));

*/
?>
2 changes: 2 additions & 0 deletions web/js/MonitorStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const streaming = [];

function MonitorStream(monitorData) {
this.id = monitorData.id;
this.name = monitorData.name;
this.connKey = monitorData.connKey;
this.url = monitorData.url;
this.url_to_zms = monitorData.url_to_zms;
Expand Down Expand Up @@ -34,6 +35,7 @@ function MonitorStream(monitorData) {
};
this.ajaxQueue = null;
this.type = monitorData.type;
this.capturing = monitorData.capturing;
this.refresh = monitorData.refresh;

this.buttons = {}; // index by name
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

15 changes: 10 additions & 5 deletions web/skins/classic/css/base/skin.css
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ ul.nav.nav-pills.flex-column {
}

#mfbpanel {
width: calc(100% - 25px);
width: calc(100% - 55px);
float: left;
}

Expand Down Expand Up @@ -1140,8 +1140,8 @@ html::-webkit-scrollbar-thumb, div::-webkit-scrollbar-thumb, nav::-webkit-scroll
/* +++ Control button block in the Stream image*/
.block-button-center {
position: absolute;
left: 35%;
right: 35%;
left: 50px;
right: 50px;
top: 2px;
z-index: 10;
}
Expand Down Expand Up @@ -1175,6 +1175,7 @@ button.btn.btn-zoom-in {
}

button.btn.btn-view-watch,
button.btn.btn-view-event,
button.btn.btn-edit-monitor,
button.btn.btn-fullscreen {
padding: 0;
Expand All @@ -1190,15 +1191,19 @@ button.btn.btn-fullscreen {
button.btn.btn-zoom-out:focus,
button.btn.btn-zoom-in:focus,
button.btn.btn-view-watch:focus,
button.btn.btn-view-event:focus,
button.btn.btn-fullscreen:focus,
button.btn.btn-edit-monitor:focus {
outline: 0;
box-shadow: none;
}

button.btn.btn-zoom-out:hover,
button.btn.btn-zoom-in:hover,
button.btn.btn-edit-monitor:hover,
button.btn.btn-view-watch:hover {
button.btn.btn-view-watch:hover,
button.btn.btn-view-event:hover,
button.btn.btn-fullscreen:hover,
button.btn.btn-edit-monitor:hover {
background-color: darkgrey;
}

Expand Down
Loading
Loading