Rough in the ui for Snapshots

This commit is contained in:
Isaac Connor 2021-03-12 09:28:05 -05:00
parent c9170a87b2
commit 11c2318a05
11 changed files with 1091 additions and 0 deletions

56
db/zm_update-1.35.21.sql Normal file
View File

@ -0,0 +1,56 @@
--
-- Add HomeView to Users
--
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Users'
AND column_name = 'HomeView'
) > 0,
"SELECT 'Column HomeView already exists in Users'",
"ALTER TABLE `Users` ADD `HomeView` varchar(64) NOT NULL DEFAULT '' AFTER `APIEnabled`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.TABLES
WHERE table_name = 'Snapshots'
AND table_schema = DATABASE()
) > 0,
"SELECT 'Snapshots table exists'",
"CREATE TABLE Snapshots (
`Id` int(10) unsigned NOT NULL auto_increment,
`Name` VARCHAR(64),
`Description` TEXT,
`CreatedBy` int(10),
`CreatedOn` datetime default NULL,
PRIMARY KEY(Id)
) ENGINE=InnoDB;"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
SET @s = (SELECT IF(
(SELECT COUNT(*)
FROM INFORMATION_SCHEMA.TABLES
WHERE table_name = 'Snapshot_Events'
AND table_schema = DATABASE()
) > 0,
"SELECT 'Snapshot_Events table exists'",
"CREATE TABLE Snapshot_Events (
`Id` int(10) unsigned NOT NULL auto_increment,
`SnapshotId` int(10) unsigned NOT NULL,
FOREIGN KEY (`SnapshotId`) REFERENCES `Snapshots` (`Id`) ON DELETE CASCADE,
`EventId` bigint unsigned NOT NULL,
FOREIGN KEY (`EventId`) REFERENCES `Events` (`Id`) ON DELETE CASCADE,
KEY `Snapshot_Events_SnapshotId_idx` (`SnapshotId`),
PRIMARY KEY(Id)
) ENGINE=InnoDB;"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

227
web/ajax/snapshots.php Normal file
View File

@ -0,0 +1,227 @@
<?php
require_once('includes/Snapshot.php');
require_once('includes/Filter.php');
$message = '';
$data = array();
//
// INITIALIZE AND CHECK SANITY
//
if ( !canView('Events') ) $message = 'Insufficient permissions for user '.$user['Username'];
if ( empty($_REQUEST['task']) ) {
$message = 'Must specify a task';
} else {
$task = $_REQUEST['task'];
}
if ( empty($_REQUEST['ids']) ) {
if ( isset($_REQUEST['task']) && $_REQUEST['task'] != 'query' ) $message = 'No snapshot id(s) supplied';
} else {
$eids = $_REQUEST['ids'];
}
if ( $message ) {
ajaxError($message);
return;
}
// Search contains a user entered string to search on
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
// Advanced search contains an array of "column name" => "search text" pairs
// Bootstrap table sends json_ecoded array, which we must decode
$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array();
// Sort specifies the name of the column to sort on
$sort = 'Id';
if ( isset($_REQUEST['sort']) ) {
$sort = $_REQUEST['sort'];
}
// Offset specifies the starting row to return, used for pagination
$offset = 0;
if ( isset($_REQUEST['offset']) ) {
if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) {
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']);
} else {
$offset = $_REQUEST['offset'];
}
}
// Order specifies the sort direction, either asc or desc
$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC';
// Limit specifies the number of rows to return
// Set the default to 0 for events view, to prevent an issue with ALL pagination
$limit = 0;
if ( isset($_REQUEST['limit']) ) {
if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) {
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
} else {
$limit = $_REQUEST['limit'];
}
}
//
// MAIN LOOP
//
switch ( $task ) {
case 'delete' :
if ( !canEdit('Events') ) {
ajaxError('Insufficient permissions for user '.$user['Username']);
return;
}
foreach ( $ids as $id ) $data[] = deleteRequest($id);
break;
case 'query' :
$data = queryRequest($search, $advsearch, $sort, $offset, $order, $limit);
break;
default :
ZM\Fatal("Unrecognised task '$task'");
} // end switch task
ajaxResponse($data);
//
// FUNCTION DEFINITIONS
//
function deleteRequest($id) {
$message = array();
$snapshot = new ZM\Snapshot($id);
if ( !$snapshot->Id() ) {
$message[] = array($id=>'Snapshot not found.');
//} else if ( $snapshot->Archived() ) {
//$message[] = array($id=>'Event is archived, cannot delete it.');
} else {
$snapshot->delete();
}
return $message;
}
function queryRequest($search, $advsearch, $sort, $offset, $order, $limit) {
$data = array(
'total' => 0,
'totalNotFiltered' => 0,
'rows' => array(),
'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG)
);
// Put server pagination code here
// The table we want our data from
$table = 'Snapshots';
// The names of the dB columns in the events table we are interested in
$columns = array('Id', 'Name', 'Description', 'CreatedDateTime');
if ( !in_array($sort, array_merge($columns)) ) {
ZM\Error('Invalid sort field: ' . $sort);
$sort = 'Id';
}
$values = array();
$likes = array();
$where = '';
$col_str = '*';
$sql = 'SELECT ' .$col_str. ' FROM `Snapshots`'.$where.' ORDER BY '.$sort.' '.$order;
$unfiltered_rows = array();
$event_ids = array();
ZM\Debug('Calling the following sql query: ' .$sql);
$query = dbQuery($sql, $values);
if ( $query ) {
while ( $row = dbFetchNext($query) ) {
$snapshot = new ZM\Snapshot($row);
$snapshot->remove_from_cache();
$snapshot_ids[] = $snapshot->Id();
$unfiltered_rows[] = $row;
} # end foreach row
}
ZM\Debug('Have ' . count($unfiltered_rows) . ' snapshots matching base filter.');
$filtered_rows = null;
if ( count($advsearch) or $search != '' ) {
$search_filter = new ZM\Filter();
$search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'Id', 'op'=>'IN', 'val'=>$event_ids));
// There are two search bars in the log view, normal and advanced
// Making an exuctive decision to ignore the normal search, when advanced search is in use
// Alternatively we could try to do both
if ( count($advsearch) ) {
$terms = array();
foreach ( $advsearch as $col=>$text ) {
$terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text);
} # end foreach col in advsearch
$terms[0]['obr'] = 1;
$terms[count($terms)-1]['cbr'] = 1;
$search_filter->addTerms($terms);
} else if ( $search != '' ) {
$search = '%' .$search. '%';
$terms = array();
foreach ( $columns as $col ) {
$terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search);
}
$terms[0]['obr'] = 1;
$terms[0]['cnj'] = 'and';
$terms[count($terms)-1]['cbr'] = 1;
$search_filter = $search_filter->addTerms($terms, array('obr'=>1, 'cbr'=>1, 'op'=>'OR'));
} # end if search
$sql = 'SELECT ' .$col_str. ' FROM `Snapshots` WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order;
ZM\Debug('Calling the following sql query: ' .$sql);
$filtered_rows = dbFetchAll($sql);
ZM\Debug('Have ' . count($filtered_rows) . ' snapshots matching search filter.');
} else {
$filtered_rows = $unfiltered_rows;
} # end if search_filter->terms() > 1
$returned_rows = array();
foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) {
$snapshot = new ZM\Snapshot($row);
//$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. '"/>';
$row['Name'] = validHtmlStr($row['Name']);
$row['Description'] = validHtmlStr($row['Description']);
//$row['Archived'] = $row['Archived'] ? translate('Yes') : translate('No');
//$row['Emailed'] = $row['Emailed'] ? translate('Yes') : translate('No');
//$row['Cause'] = validHtmlStr($row['Cause']);
$row['CreatedOn'] = strftime(STRF_FMT_DATETIME_SHORTER, strtotime($row['CreatedOn']));
//$row['StartDateTime'] = strftime(STRF_FMT_DATETIME_SHORTER, strtotime($row['StartDateTime']));
//$row['EndDateTime'] = $row['EndDateTime'] ? strftime(STRF_FMT_DATETIME_SHORTER, strtotime($row['EndDateTime'])) : null;
//$row['Length'] = gmdate('H:i:s', $row['Length'] );
//$row['Storage'] = ( $row['StorageId'] and isset($StorageById[$row['StorageId']]) ) ? $StorageById[$row['StorageId']]->Name() : 'Default';
//$row['Notes'] = nl2br(htmlspecialchars($row['Notes']));
//$row['DiskSpace'] = human_filesize($event->DiskSpace());
$returned_rows[] = $row;
} # end foreach row matching search
$data['rows'] = $returned_rows;
# totalNotFiltered must equal total, except when either search bar has been used
$data['totalNotFiltered'] = count($unfiltered_rows);
if ( $search != '' || count($advsearch) ) {
$data['total'] = count($filtered_rows);
} else {
$data['total'] = $data['totalNotFiltered'];
}
return $data;
}
?>

65
web/includes/Snapshot.php Normal file
View File

@ -0,0 +1,65 @@
<?php
namespace ZM;
require_once('database.php');
require_once('Object.php');
require_once('Event.php');
class Snapshot extends ZM_Object {
protected static $table = 'Snapshots';
protected $defaults = array(
'Id' => null,
'CreatedBy' => null,
'CreatedOn' => 'NOW()',
'Name' => '',
'Description' => '',
);
public static function find( $parameters = array(), $options = array() ) {
return ZM_Object::_find(get_class(), $parameters, $options);
}
public static function find_one( $parameters = array(), $options = array() ) {
return ZM_Object::_find_one(get_class(), $parameters, $options);
}
public function Event() {
return new Event( $this->{'EventId'} );
}
public function delete() {
if ( property_exists($this, 'Id') ) {
dbQuery('DELETE FROM `Snapshot_Events` WHERE `SnapshotId`=?', array($this->{'Id'}));
}
}
public function EventIds( ) {
if ( ! property_exists($this, 'EventIds') ) {
$this->{'EventIds'} = dbFetchAll('SELECT `EventId` FROM `Snapshot_Events` WHERE `SnapshotId`=?', 'EventId', array($this->{'Id'}));
}
return $this->{'EventIds'};
}
public function Events() {
if ( ! property_exists($this, 'Events') ) {
$this->{'Events'} = Event::find(array('Id'=>$this->EventIds()));
}
return $this->{'Events'};
}
} # end class Snapshot
class Snapshot_Event extends ZM_Object {
protected static $table = 'Snapshot_Events';
protected $defaults = array(
'Id' => null,
'EventId' => 0,
'SnapshotId' => 0,
);
public static function find( $parameters = array(), $options = array() ) {
return ZM_Object::_find(get_class(), $parameters, $options);
}
public static function find_one( $parameters = array(), $options = array() ) {
return ZM_Object::_find_one(get_class(), $parameters, $options);
}
} # end class Snapshot_Event
?>

View File

@ -0,0 +1,73 @@
<?php
//
// ZoneMinder web action
// Copyright (C) 2019 ZoneMinder LLC
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
require_once('includes/Snapshot.php');
require_once('includes/Monitor.php');
if ( $action == 'create' ) {
if ( ! (isset($_REQUEST['monitor_ids']) and count($_REQUEST['monitor_ids']) > 0 ) ) {
ZM\Error('No monitor ids given in snapshot creation request');
return;
}
$snapshot = new ZM\Snapshot();
$snapshot->save(array('CreatedBy'=>$user['Id']));
foreach ( $_REQUEST['monitor_ids'] as $monitor_id ) {
$snapshot_event = new ZM\Snapshot_Event();
$monitor = new ZM\Monitor($monitor_id);
$event_id = $monitor->TriggerOn();
ZM\Debug("Have event $event_id for monitor $monitor_id");
if ( $event_id ) {
$snapshot_event->save(array(
'SnapshotId'=>$snapshot->Id(),
'EventId'=>$event_id
));
}
} # end foreach monitor
foreach ( $_REQUEST['monitor_ids'] as $monitor_id ) {
$monitor = new ZM\Monitor($monitor_id);
$monitor->TriggerOff();
}
$redirect = '?view=snapshot&id='.$snapshot->Id();
return;
}
// Event scope actions, view permissions only required
if ( isset($_REQUEST['id']) ) {
$snapshot = new ZM\Snapshot($_REQUEST['id']);
if ( ($action == 'save') ) {
if ( canEdit('Events') or $snapshot->CreatedBy() == $user['Id'] ) {
$changes = $snapshot->changes($_REQUEST['snapshot']);
if ( count($changes) ) {
$snapshot->save($changes);
}
$redirect = '?view=snapshots';
}
} else if ( $action == 'delete' ) {
if ( canEdit('Events') ) {
$snapshot->delete();
$redirect = '?view=snapshots';
}
}
} // end if canEdit(Events)
?>

View File

@ -0,0 +1,17 @@
#content {
margin: 0 15px;
}
.Name {
width: 600px;
}
.Name input {
width: 100%;
}
.Description {
width: 600px;
}
.Description textarea {
width: 100%;
}

View File

@ -0,0 +1,115 @@
var backBtn = $j('#backBtn');
var saveBtn = $j('#saveBtn');
var deleteBtn = $j('#deleteBtn');
// Manage the DELETE CONFIRMATION modal button
function manageDelConfirmModalBtns() {
document.getElementById('delConfirmBtn').addEventListener('click', function onDelConfirmClick(evt) {
if ( !canEdit.Events ) {
enoperm();
return;
}
evt.preventDefault();
/*
$j.getJSON(thisUrl + '?request=events&task=delete&eids[]='+eventData.Id)
.done(function(data) {
streamNext(true);
})
.fail(logAjaxFail);
*/
});
// Manage the CANCEL modal button
document.getElementById("delCancelBtn").addEventListener("click", function onDelCancelClick(evt) {
$j('#deleteConfirm').modal('hide');
});
}
function initPage() {
// enable or disable buttons based on current selection and user rights
/*
renameBtn.prop('disabled', !canEdit.Events);
archiveBtn.prop('disabled', !(!eventData.Archived && canEdit.Events));
unarchiveBtn.prop('disabled', !(eventData.Archived && canEdit.Events));
*/
saveBtn.prop('disabled', !(canEdit.Events || (snapshot.CreatedBy == user.Id) ));
/*
exportBtn.prop('disabled', !canView.Events);
downloadBtn.prop('disabled', !canView.Events);
*/
deleteBtn.prop('disabled', !canEdit.Events);
// Don't enable the back button if there is no previous zm page to go back to
backBtn.prop('disabled', !document.referrer.length);
// Manage the BACK button
bindButton('#backBtn', 'click', null, function onBackClick(evt) {
evt.preventDefault();
window.history.back();
});
// Manage the REFRESH Button
bindButton('#refreshBtn', 'click', null, function onRefreshClick(evt) {
evt.preventDefault();
window.location.reload(true);
});
// Manage the EDIT button
bindButton('#saveBtn', 'click', null, function onSaveClick(evt) {
/*
if ( ! canEdit.Events ) {
enoperm();
return;
}
*/
console.log(evt);
evt.target.form.submit();
});
/*
// Manage the EXPORT button
bindButton('#exportBtn', 'click', null, function onExportClick(evt) {
evt.preventDefault();
window.location.assign('?view=export&eids[]='+eventData.Id);
});
// Manage the DOWNLOAD VIDEO button
bindButton('#downloadBtn', 'click', null, function onDownloadClick(evt) {
evt.preventDefault();
$j.getJSON(thisUrl + '?request=modal&modal=download&eids[]='+eventData.Id)
.done(function(data) {
insertModalHtml('downloadModal', data.html);
$j('#downloadModal').modal('show');
// Manage the GENERATE DOWNLOAD button
$j('#exportButton').click(exportEvent);
})
.fail(logAjaxFail);
});
*/
// Manage the DELETE button
bindButton('#deleteBtn', 'click', null, function onDeleteClick(evt) {
if ( !canEdit.Events ) {
enoperm();
return;
}
evt.preventDefault();
if ( ! $j('#deleteConfirm').length ) {
// Load the delete confirmation modal into the DOM
$j.getJSON(thisUrl + '?request=modal&modal=delconfirm')
.done(function(data) {
insertModalHtml('deleteConfirm', data.html);
manageDelConfirmModalBtns();
$j('#deleteConfirm').modal('show');
})
.fail(logAjaxFail);
return;
}
$j('#deleteConfirm').modal('show');
});
} // end initPage
// Kick everything off
$j(document).ready(initPage);

View File

@ -0,0 +1,30 @@
<?php
global $snapshot;
?>
var snapshot = <?php echo json_encode($snapshot); ?>;
var eventDataStrings = {
Id: '<?php echo translate('EventId') ?>',
Name: '<?php echo translate('EventName') ?>',
MonitorId: '<?php echo translate('AttrMonitorId') ?>',
MonitorName: '<?php echo translate('AttrMonitorName') ?>',
Cause: '<?php echo translate('Cause') ?>',
StartDateTimeFmt: '<?php echo translate('AttrStartTime') ?>',
Length: '<?php echo translate('Duration') ?>',
Frames: '<?php echo translate('AttrFrames') ?>',
AlarmFrames: '<?php echo translate('AttrAlarmFrames') ?>',
TotScore: '<?php echo translate('AttrTotalScore') ?>',
AvgScore: '<?php echo translate('AttrAvgScore') ?>',
MaxScore: '<?php echo translate('AttrMaxScore') ?>',
DiskSpace: '<?php echo translate('DiskSpace') ?>',
Storage: '<?php echo translate('Storage') ?>',
ArchivedStr: '<?php echo translate('Archived') ?>',
EmailedStr: '<?php echo translate('Emailed') ?>'
};
// Strings
//
var deleteString = "<?php echo validJsStr(translate('Delete')) ?>";
var causeString = "<?php echo validJsStr(translate('AttrCause')) ?>";
var WEB_LIST_THUMB_WIDTH = '<?php echo ZM_WEB_LIST_THUMB_WIDTH ?>';
var WEB_LIST_THUMB_HEIGHT = '<?php echo ZM_WEB_LIST_THUMB_HEIGHT ?>';

View File

@ -0,0 +1,312 @@
var backBtn = $j('#backBtn');
var exportBtn = $j('#exportBtn');
var deleteBtn = $j('#deleteBtn');
var table = $j('#snapshotTable');
/*
This is the format of the json object sent by bootstrap-table
var params =
{
"type":"get",
"data":
{
"search":"some search text",
"sort":"StartDateTime",
"order":"asc",
"offset":0,
"limit":25
"filter":
{
"Name":"some advanced search text"
"StartDateTime":"some more advanced search text"
}
},
"cache":true,
"contentType":"application/json",
"dataType":"json"
};
*/
// Called by bootstrap-table to retrieve zm event data
function ajaxRequest(params) {
if ( params.data && params.data.filter ) {
params.data.advsearch = params.data.filter;
delete params.data.filter;
}
$j.getJSON(thisUrl + '?view=request&request=snapshots&task=query', params.data)
.done(function(data) {
var rows = processRows(data.rows);
// rearrange the result into what bootstrap-table expects
params.success({total: data.total, totalNotFiltered: data.totalNotFiltered, rows: rows});
})
.fail(function(jqXHR) {
logAjaxFail(jqXHR);
table.bootstrapTable('refresh');
});
}
function processRows(rows) {
$j.each(rows, function(ndx, row) {
var id = row.Id;
row.Id = '<a href="?view=snapshot&amp;id=' + id + '">' + id + '</a>';
row.Name = '<a href="?view=snapshot&amp;id=' + id + '">' + row.Name + '</a>';
row.Description = '<a href="?view=snapshot&amp;id=' + id + '">' + row.Description + '</a>';
//if ( WEB_LIST_THUMBS ) row.Thumbnail = '<a href="?view=snapshot&amp;id=' + row.Id '">' + row.imgHtml + '</a>';
});
return rows;
}
// Returns the event id's of the selected rows
function getIdSelections() {
var table = $j('#snapshotTable');
return $j.map(table.bootstrapTable('getSelections'), function(row) {
return row.Id.replace(/(<([^>]+)>)/gi, ''); // strip the html from the element before sending
});
}
// Returns a boolen to indicate at least one selected row is archived
function getArchivedSelections() {
var table = $j('#snapshotTable');
var selection = $j.map(table.bootstrapTable('getSelections'), function(row) {
return row.Archived;
});
return selection.includes("Yes");
}
// Load the Delete Confirmation Modal HTML via Ajax call
function getDelConfirmModal() {
$j.getJSON(thisUrl + '?request=modal&modal=delconfirm')
.done(function(data) {
insertModalHtml('deleteConfirm', data.html);
manageDelConfirmModalBtns();
})
.fail(logAjaxFail);
}
// Manage the DELETE CONFIRMATION modal button
function manageDelConfirmModalBtns() {
document.getElementById("delConfirmBtn").addEventListener("click", function onDelConfirmClick(evt) {
if ( ! canEdit.Events ) {
enoperm();
return;
}
var selections = getIdSelections();
evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&task=delete&eids[]='+selections.join('&eids[]='))
.done( function(data) {
$j('#snapshotTable').bootstrapTable('refresh');
$j('#deleteConfirm').modal('hide');
})
.fail( function(jqxhr) {
logAjaxFail(jqxhr);
$j('#snapshotTable').bootstrapTable('refresh');
$j('#deleteConfirm').modal('hide');
});
});
// Manage the CANCEL modal button
document.getElementById("delCancelBtn").addEventListener("click", function onDelCancelClick(evt) {
$j('#deleteConfirm').modal('hide');
});
}
function getEventDetailModal(eid) {
$j.getJSON(thisUrl + '?request=modal&modal=eventdetail&eids[]=' + eid)
.done(function(data) {
insertModalHtml('eventDetailModal', data.html);
$j('#eventDetailModal').modal('show');
// Manage the Save button
$j('#eventDetailSaveBtn').click(function(evt) {
evt.preventDefault();
$j('#eventDetailForm').submit();
});
})
.fail(logAjaxFail);
}
function getObjdetectModal(eid) {
$j.getJSON(thisUrl + '?request=modal&modal=objdetect&eid=' + eid)
.done(function(data) {
insertModalHtml('objdetectModal', data.html);
$j('#objdetectModal').modal('show');
})
.fail(logAjaxFail);
}
function initPage() {
// Remove the thumbnail column from the DOM if thumbnails are off globally
if ( !WEB_LIST_THUMBS ) $j('th[data-field="Thumbnail"]').remove();
// Load the delete confirmation modal into the DOM
getDelConfirmModal();
// Init the bootstrap-table
table.bootstrapTable({icons: icons});
// Hide these columns on first run when no cookie is saved
if ( !getCookie("zmEventsTable.bs.table.columns") ) {
table.bootstrapTable('hideColumn', 'Archived');
table.bootstrapTable('hideColumn', 'Emailed');
}
// enable or disable buttons based on current selection and user rights
table.on('check.bs.table uncheck.bs.table ' +
'check-all.bs.table uncheck-all.bs.table',
function() {
selections = table.bootstrapTable('getSelections');
viewBtn.prop('disabled', !(selections.length && canView.Events));
archiveBtn.prop('disabled', !(selections.length && canEdit.Events));
unarchiveBtn.prop('disabled', !(getArchivedSelections()) && canEdit.Events);
editBtn.prop('disabled', !(selections.length && canEdit.Events));
exportBtn.prop('disabled', !(selections.length && canView.Events));
downloadBtn.prop('disabled', !(selections.length && canView.Events));
deleteBtn.prop('disabled', !(selections.length && canEdit.Events));
});
// Don't enable the back button if there is no previous zm page to go back to
backBtn.prop('disabled', !document.referrer.length);
// Setup the thumbnail video animation
initThumbAnimation();
// Some toolbar events break the thumbnail animation, so re-init eventlistener
table.on('all.bs.table', initThumbAnimation);
// Manage the BACK button
document.getElementById("backBtn").addEventListener("click", function onBackClick(evt) {
evt.preventDefault();
window.history.back();
});
// Manage the REFRESH Button
document.getElementById("refreshBtn").addEventListener("click", function onRefreshClick(evt) {
evt.preventDefault();
window.location.reload(true);
});
/*
// Manage the ARCHIVE button
document.getElementById("archiveBtn").addEventListener("click", function onArchiveClick(evt) {
var selections = getIdSelections();
evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&task=archive&eids[]='+selections.join('&eids[]='))
.done( function(data) {
$j('#snapshotTable').bootstrapTable('refresh');
})
.fail(logAjaxFail);
});
// Manage the UNARCHIVE button
document.getElementById("unarchiveBtn").addEventListener("click", function onUnarchiveClick(evt) {
if ( ! canEdit.Events ) {
enoperm();
return;
}
var selections = getIdSelections();
//console.log(selections);
evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&task=unarchive&eids[]='+selections.join('&eids[]='))
.done( function(data) {
$j('#snapshotTable').bootstrapTable('refresh');
})
.fail(logAjaxFail);
});
// Manage the EDIT button
document.getElementById("editBtn").addEventListener("click", function onEditClick(evt) {
if ( ! canEdit.Events ) {
enoperm();
return;
}
var selections = getIdSelections();
evt.preventDefault();
$j.getJSON(thisUrl + '?request=modal&modal=eventdetail&eids[]='+selections.join('&eids[]='))
.done(function(data) {
insertModalHtml('eventDetailModal', data.html);
$j('#eventDetailModal').modal('show');
// Manage the Save button
$j('#eventDetailSaveBtn').click(function(evt) {
evt.preventDefault();
$j('#eventDetailForm').submit();
});
})
.fail(logAjaxFail);
});
// Manage the EXPORT button
document.getElementById("exportBtn").addEventListener("click", function onExportClick(evt) {
var selections = getIdSelections();
evt.preventDefault();
window.location.assign('?view=export&eids[]='+selections.join('&eids[]='));
});
// Manage the DOWNLOAD VIDEO button
document.getElementById("downloadBtn").addEventListener("click", function onDownloadClick(evt) {
var selections = getIdSelections();
evt.preventDefault();
$j.getJSON(thisUrl + '?request=modal&modal=download&eids[]='+selections.join('&eids[]='))
.done(function(data) {
insertModalHtml('downloadModal', data.html);
$j('#downloadModal').modal('show');
// Manage the GENERATE DOWNLOAD button
$j('#exportButton').click(exportEvent);
})
.fail(logAjaxFail);
});
*/
// Manage the DELETE button
document.getElementById("deleteBtn").addEventListener("click", function onDeleteClick(evt) {
if ( ! canEdit.Events ) {
enoperm();
return;
}
evt.preventDefault();
$j('#deleteConfirm').modal('show');
});
// Update table links each time after new data is loaded
table.on('post-body.bs.table', function(data) {
// Manage the Object Detection links in the events list
$j(".objDetectLink").click(function(evt) {
evt.preventDefault();
var eid = $j(this).data('eid');
getObjdetectModal(eid);
});
// Manage the eventdetail links in the events list
$j(".eDetailLink").click(function(evt) {
evt.preventDefault();
var eid = $j(this).data('eid');
getEventDetailModal(eid);
});
var thumb_ndx = $j('#snapshotTable tr th').filter(function() {
return $j(this).text().trim() == 'Thumbnail';
}).index();
table.find("tr td:nth-child(" + (thumb_ndx+1) + ")").addClass('colThumbnail');
});
table.bootstrapTable('resetSearch');
// The table is initially given a hidden style, so now that we are done rendering, show it
table.show();
}
$j(document).ready(function() {
initPage();
});

View File

@ -0,0 +1,6 @@
var confirmDeleteEventsString = "<?php echo addslashes(translate('ConfirmDeleteEvents')) ?>";
var archivedString = "<?php echo translate('Archived') ?>";
var emailedString = "<?php echo translate('Emailed') ?>";
var yesString = "<?php echo translate('Yes') ?>";
var noString = "<?php echo translate('No') ?>";
var WEB_LIST_THUMBS = <?php echo ZM_WEB_LIST_THUMBS?'true':'false' ?>;

View File

@ -0,0 +1,98 @@
<?php
//
// ZoneMinder web snapshot view file, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
if ( !canView('Events') ) {
$view = 'error';
return;
}
require_once('includes/Event.php');
require_once('includes/Filter.php');
require_once('includes/Snapshot.php');
$id = isset($_REQUEST['id']) ? validInt($_REQUEST['id']) : null;
$snapshot = new ZM\Snapshot($id);
$monitors = array();
if ( $user['MonitorIds'] ) {
$monitor_ids = explode(',', $user['MonitorIds']);
}
xhtmlHeaders(__FILE__, translate('Snapshot').' '.$snapshot->Id());
?>
<body>
<div id="page">
<?php echo getNavBarHTML() ?>
<?php
if ( !$snapshot->Id() ) {
echo '<div class="error">Snapshot was not found.</div>';
}
?>
<!-- BEGIN HEADER -->
<form action="?view=snapshot&id=<?php echo $snapshot->Id() ?>" method="post">
<input type="hidden" name="action" value="save"/>
<div class="d-flex flex-row justify-content-between px-3 py-1">
<div id="toolbar">
<button id="backBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Back') ?>" disabled><i class="fa fa-arrow-left"></i></button>
<button id="refreshBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Refresh') ?>" ><i class="fa fa-refresh"></i></button>
<?php if ( $snapshot->Id() ) { ?>
<!--
<button id="editBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Edit') ?>" disabled><i class="fa fa-pencil"></i></button>
-->
<button id="saveBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Save') ?>"><i class="fa fa-save"></i></button>
<!--
<button id="exportBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Export') ?>"><i class="fa fa-external-link"></i></button>
-->
<button id="deleteBtn" class="btn btn-danger" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Delete') ?>"><i class="fa fa-trash"></i></button>
<?php } // end if snapshot->Id ?>
</div>
<h2><?php echo translate('Snapshot').' '.$snapshot->Id() ?></h2>
</div>
<div class="d-flex flex-row justify-content-between py-1">
<!--
<div class="form-group"><label><?php echo translate('Created By') ?></label>
-->
<div class="form-group CreatedOn"><label><?php echo translate('Created On') ?></label>
<?php echo $snapshot->CreatedOn() ?>
</div>
<div class="form-group Name"><label><?php echo translate('Reference') ?></label>
<input type="text" name="snapshot[Name]" value="<?php echo validHtmlStr($snapshot->Name()); ?>"/>
</div>
<div class="form-group Description"><label>
<?php echo translate('Notes') ?></label>
<textarea name="snapshot[Description]"><?php echo validHtmlStr($snapshot->Description()); ?></textarea>
</div>
</div>
<?php if ( $snapshot->Id() ) { ?>
<!-- BEGIN VIDEO CONTENT ROW -->
<div id="content" class="justify-content-center">
<?php
$events = $snapshot->Events();
$width = 100 / ( ( count($events) < 4 ) ? count($events) : 4 ) -1;
foreach ( $snapshot->Events() as $event ) {
$imgSrc = $event->getThumbnailSrc(array(), '&amp;');
echo '<img src="?view=image&eid='.$event->Id().'&fid=snapshot" width="'.$width.'%"/>';
}
?>
</div><!--content-->
<?php } // end if snapshot->Id() ?>
</form>
</div><!--page-->
<?php xhtmlFooter() ?>

View File

@ -0,0 +1,92 @@
<?php
//
// ZoneMinder web snapshots view file
// Copyright (C) 2021 Isaac Connor
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
if ( !canView('Events') || (!empty($_REQUEST['execute']) && !canEdit('Events')) ) {
$view = 'error';
return;
}
require_once('includes/Event.php');
require_once('includes/Snapshot.php');
xhtmlHeaders(__FILE__, translate('Snapshots'));
getBodyTopHTML();
?>
<?php echo getNavBarHTML() ?>
<div id="page" class="container-fluid p-3">
<!-- Toolbar button placement and styling handled by bootstrap-tables -->
<div id="toolbar">
<button id="backBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Back') ?>" disabled><i class="fa fa-arrow-left"></i></button>
<button id="refreshBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Refresh') ?>" ><i class="fa fa-refresh"></i></button>
<!--<button id="filterBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Filter') ?>"><i class="fa fa-filter"></i></button>-->
<!--<button id="exportBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Export') ?>" disabled><i class="fa fa-external-link"></i></button>-->
<button id="deleteBtn" class="btn btn-danger" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Delete') ?>" disabled><i class="fa fa-trash"></i></button>
</div>
<!-- Table styling handled by bootstrap-tables -->
<div class="row justify-content-center table-responsive-sm">
<table
id="snapshotTable"
data-locale="<?php echo i18n() ?>"
data-side-pagination="server"
data-ajax="ajaxRequest"
data-pagination="true"
data-show-pagination-switch="true"
data-page-list="[10, 25, 50, 100, 200, All]"
data-search="true"
data-cookie="true"
data-cookie-id-table="zmSnapshotsTable"
data-cookie-expire="2y"
data-click-to-select="true"
data-remember-order="true"
data-show-columns="true"
data-show-export="true"
data-uncheckAll="true"
data-toolbar="#toolbar"
data-show-fullscreen="true"
data-click-to-select="true"
data-maintain-meta-data="true"
data-buttons-class="btn btn-normal"
data-show-jump-to="true"
data-show-refresh="true"
class="table-sm table-borderless"
style="display:none;"
>
<thead>
<!-- Row styling is handled by bootstrap-tables -->
<tr>
<th data-sortable="false" data-field="toggleCheck" data-checkbox="true"></th>
<th data-sortable="true" data-field="Id"><?php echo translate('Id') ?></th>
<th data-sortable="true" data-field="Name"><?php echo translate('Reference') ?></th>
<th data-sortable="false" data-field="Description"><?php echo translate('Notes') ?></th>
<th data-sortable="true" data-field="CreatedOn"><?php echo translate('AttrWhen') ?></th>
<th data-sortable="false" data-field="Thumbnail"><?php echo translate('Thumbnail') ?></th>
</tr>
</thead>
<tbody>
<!-- Row data populated via Ajax -->
</tbody>
</table>
</div>
</div>
<?php xhtmlFooter() ?>