Merge branch 'master' into add_janus_rtsp_user
commit
d1cd7d3f91
|
@ -1,18 +1,25 @@
|
||||||
--
|
--
|
||||||
-- Update Monitors table to have a MQTT_Enabled Column
|
-- This adds the Reports Table
|
||||||
--
|
--
|
||||||
|
|
||||||
SELECT 'Checking for `Janus_RTSP_User` in Monitors';
|
|
||||||
SET @s = (SELECT IF(
|
SET @s = (SELECT IF(
|
||||||
(SELECT COUNT(*)
|
(SELECT COUNT(*)
|
||||||
FROM INFORMATION_SCHEMA.COLUMNS
|
FROM INFORMATION_SCHEMA.TABLES
|
||||||
WHERE table_name = 'Monitors'
|
WHERE table_name = 'Reports'
|
||||||
AND table_schema = DATABASE()
|
AND table_schema = DATABASE()
|
||||||
AND column_name = 'Janus_RTSP_User'
|
) > 0,
|
||||||
) > 0,
|
"SELECT 'Reports table exists'",
|
||||||
"SELECT 'Column Janus_RTSP_User already exists in Monitors'",
|
"
|
||||||
"ALTER TABLE Monitors ADD COLUMN `Janus_RTSP_User` INT(10) AFTER `Janus_Use_RTSP_Restream`"
|
CREATE TABLE Reports (
|
||||||
));
|
Id INT(10) UNSIGNED auto_increment,
|
||||||
|
Name varchar(30),
|
||||||
|
FilterId int(10) UNSIGNED,
|
||||||
|
`StartDateTime` datetime default NULL,
|
||||||
|
`EndDateTime` datetime default NULL,
|
||||||
|
`Interval` INT(10) UNSIGNED,
|
||||||
|
PRIMARY KEY(Id)
|
||||||
|
) ENGINE=InnoDB;"
|
||||||
|
));
|
||||||
|
|
||||||
PREPARE stmt FROM @s;
|
PREPARE stmt FROM @s;
|
||||||
EXECUTE stmt;
|
EXECUTE stmt;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
--
|
||||||
|
-- Update Monitors table to have a Janus_RTSP_User Column
|
||||||
|
--
|
||||||
|
|
||||||
|
SELECT 'Checking for `Janus_RTSP_User` in Monitors';
|
||||||
|
SET @s = (SELECT IF(
|
||||||
|
(SELECT COUNT(*)
|
||||||
|
FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE table_name = 'Monitors'
|
||||||
|
AND table_schema = DATABASE()
|
||||||
|
AND column_name = 'Janus_RTSP_User'
|
||||||
|
) > 0,
|
||||||
|
"SELECT 'Column Janus_RTSP_User already exists in Monitors'",
|
||||||
|
"ALTER TABLE Monitors ADD COLUMN `Janus_RTSP_User` INT(10) AFTER `Janus_Use_RTSP_Restream`"
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE stmt FROM @s;
|
||||||
|
EXECUTE stmt;
|
|
@ -275,7 +275,7 @@ sub zmMemVerify {
|
||||||
}
|
}
|
||||||
$valid = zmMemRead($monitor, 'shared_data:valid', 1);
|
$valid = zmMemRead($monitor, 'shared_data:valid', 1);
|
||||||
if (!$valid) {
|
if (!$valid) {
|
||||||
Error("Shared data not valid for monitor $$monitor{Id}");
|
Debug(1, "Shared data not valid for monitor $$monitor{Id}");
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -87,7 +87,7 @@ sub zmMemAttach {
|
||||||
|
|
||||||
my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id};
|
my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id};
|
||||||
if ( ! -e $mmap_file ) {
|
if ( ! -e $mmap_file ) {
|
||||||
Error("Memory map file '$mmap_file' does not exist in zmMemAttach. zmc might not be running.");
|
Debug(1, "Memory map file '$mmap_file' does not exist in zmMemAttach. zmc might not be running.");
|
||||||
return undef;
|
return undef;
|
||||||
}
|
}
|
||||||
my $mmap_file_size = -s $mmap_file;
|
my $mmap_file_size = -s $mmap_file;
|
||||||
|
|
|
@ -1070,6 +1070,7 @@ void EventStream::runStream() {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Debug(1, "invalid curr_frame_id %d !< %d", curr_frame_id, event_data->frame_count);
|
Debug(1, "invalid curr_frame_id %d !< %d", curr_frame_id, event_data->frame_count);
|
||||||
|
curr_frame_id = event_data->frame_count;
|
||||||
} // end if not at end of event
|
} // end if not at end of event
|
||||||
} else {
|
} else {
|
||||||
// Paused
|
// Paused
|
||||||
|
|
|
@ -345,7 +345,6 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
|
||||||
++it;
|
++it;
|
||||||
} // end while
|
} // end while
|
||||||
|
|
||||||
|
|
||||||
Debug(1, "Resulting it pointing at latest packet? %d, next front points to begin? %d, Keyframe interval %d",
|
Debug(1, "Resulting it pointing at latest packet? %d, next front points to begin? %d, Keyframe interval %d",
|
||||||
( *it == add_packet ),
|
( *it == add_packet ),
|
||||||
( next_front == pktQueue.begin() ),
|
( next_front == pktQueue.begin() ),
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
//
|
//
|
||||||
global $CLANG;
|
global $CLANG;
|
||||||
?>
|
?>
|
||||||
<div id="modalLogout" class="modal" tabindex="-1" role="dialog">
|
<div id="modalLogout" class="modal fade" tabindex="-1" role="dialog">
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
|
|
@ -0,0 +1,209 @@
|
||||||
|
<?php
|
||||||
|
ini_set('display_errors', '');
|
||||||
|
$message = '';
|
||||||
|
$data = array();
|
||||||
|
|
||||||
|
//
|
||||||
|
// INITIALIZE AND CHECK SANITY
|
||||||
|
//
|
||||||
|
|
||||||
|
if (!canView('Events'))
|
||||||
|
$message = 'Insufficient permissions for user '.$user['Username'].'<br/>';
|
||||||
|
|
||||||
|
if (empty($_REQUEST['task'])) {
|
||||||
|
$message = 'Must specify a task<br/>';
|
||||||
|
} else {
|
||||||
|
$task = $_REQUEST['task'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($_REQUEST['ids'])) {
|
||||||
|
if (isset($_REQUEST['task']) && $_REQUEST['task'] != 'query')
|
||||||
|
$message = 'No id(s) supplied<br/>';
|
||||||
|
} else {
|
||||||
|
$ids = $_REQUEST['ids'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($message) {
|
||||||
|
ajaxError($message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once('includes/Filter.php');
|
||||||
|
require_once('includes/Report.php');
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// Order specifies the sort direction, either asc or desc
|
||||||
|
if (isset($_REQUEST['order'])) {
|
||||||
|
if (strtolower($_REQUEST['order']) == 'asc') {
|
||||||
|
$order = 'ASC';
|
||||||
|
} else if (strtolower($_REQUEST['order']) == 'desc') {
|
||||||
|
$order = 'DESC';
|
||||||
|
} else {
|
||||||
|
Warning('Invalid value for order ' . $_REQUEST['order']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort specifies the name of the column to sort on
|
||||||
|
$sort = (isset($_REQUEST['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'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit specifies the number of rows to return
|
||||||
|
// Set the default to 0 for reports 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) {
|
||||||
|
$message = deleteRequest($id);
|
||||||
|
if (count($message)) {
|
||||||
|
$data[] = $message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
$report = new ZM\Report($id);
|
||||||
|
if ( !$report->Id() ) {
|
||||||
|
$message[] = array($id=>'Report not found.');
|
||||||
|
} else if (!$report->canEdit()) {
|
||||||
|
$message[] = array($id=>'You do not have permission to delete report '.$report->Id());
|
||||||
|
} else {
|
||||||
|
$report->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryRequest($search, $advsearch, $sort, $offset, $order, $limit) {
|
||||||
|
global $dateTimeFormatter;
|
||||||
|
$data = array(
|
||||||
|
'total' => 0,
|
||||||
|
'totalNotFiltered' => 0,
|
||||||
|
'rows' => array(),
|
||||||
|
'updated' => $dateTimeFormatter->format(time())
|
||||||
|
);
|
||||||
|
|
||||||
|
// Put server pagination code here
|
||||||
|
// The table we want our data from
|
||||||
|
$table = 'Reports';
|
||||||
|
|
||||||
|
// The names of the dB columns in the reports table we are interested in
|
||||||
|
$columns = array('Id', 'Name', 'FilterId', 'StartDateTime', 'EndDateTime', 'Interval');
|
||||||
|
|
||||||
|
if ($sort != '') {
|
||||||
|
if (!in_array($sort, $columns)) {
|
||||||
|
ZM\Error('Invalid sort field: ' . $sort);
|
||||||
|
$sort = '';
|
||||||
|
} else if ($sort == 'EndDateTime') {
|
||||||
|
if ($order == 'ASC') {
|
||||||
|
$sort = 'EndDateTime IS NULL, E.EndDateTime';
|
||||||
|
} else {
|
||||||
|
$sort = 'EndDateTime IS NOT NULL, E.EndDateTime';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$values = array();
|
||||||
|
$likes = array();
|
||||||
|
$where = '';
|
||||||
|
|
||||||
|
$col_str = '*';
|
||||||
|
$sql = 'SELECT ' .$col_str. ' FROM `Reports` '.$where.($sort?' ORDER BY '.$sort.' '.$order:'');
|
||||||
|
if ($limit) $sql .= ' LIMIT '.$limit;
|
||||||
|
|
||||||
|
$unfiltered_rows = array();
|
||||||
|
$ids = array();
|
||||||
|
|
||||||
|
ZM\Debug('Calling the following sql query: ' .$sql);
|
||||||
|
$query = dbQuery($sql, $values);
|
||||||
|
if (!$query) {
|
||||||
|
ajaxError(dbError($sql));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while ($row = dbFetchNext($query)) {
|
||||||
|
$request = new ZM\Report($row);
|
||||||
|
$request->remove_from_cache();
|
||||||
|
$ids[] = $request->Id();
|
||||||
|
$unfiltered_rows[] = $row;
|
||||||
|
} # end foreach row
|
||||||
|
|
||||||
|
# Filter limits come before pagination limits.
|
||||||
|
if ($limit and ($limit > count($unfiltered_rows))) {
|
||||||
|
ZM\Debug('Filtering rows due to filter->limit '.count($unfiltered_rows).' limit: '.$limit);
|
||||||
|
$unfiltered_rows = array_slice($unfiltered_rows, 0, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
ZM\Debug('Have ' . count($unfiltered_rows) . ' reports matching base filter.');
|
||||||
|
$filtered_rows = $unfiltered_rows;
|
||||||
|
|
||||||
|
if ($limit) {
|
||||||
|
ZM\Debug("Filtering rows due to limit " . count($filtered_rows)." offset: $offset limit: $limit");
|
||||||
|
$filtered_rows = array_slice($filtered_rows, $offset, $limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
$returned_rows = array();
|
||||||
|
foreach ($filtered_rows as $row) {
|
||||||
|
$report = new ZM\Report($row);
|
||||||
|
|
||||||
|
$row['Name'] = validHtmlStr($row['Name']);
|
||||||
|
$row['StartDateTime'] = $dateTimeFormatter->format(strtotime($row['StartDateTime']));
|
||||||
|
$row['EndDateTime'] = $row['EndDateTime'] ? $dateTimeFormatter->format(strtotime($row['EndDateTime'])) : null;
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
?>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
namespace ZM;
|
||||||
|
require_once('database.php');
|
||||||
|
require_once('Object.php');
|
||||||
|
|
||||||
|
class Report extends ZM_Object {
|
||||||
|
protected static $table = 'Reports';
|
||||||
|
|
||||||
|
protected $defaults = array(
|
||||||
|
'Id' => null,
|
||||||
|
'Name' => '',
|
||||||
|
'FilterId' => null,
|
||||||
|
'StartDateTime' => null,
|
||||||
|
'EndDateTime' => null,
|
||||||
|
'Interval' => '86400',
|
||||||
|
);
|
||||||
|
|
||||||
|
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 Report
|
||||||
|
?>
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
//
|
||||||
|
// ZoneMinder web action file
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
// System edit actions
|
||||||
|
//if (!canEdit('System') ) {
|
||||||
|
//ZM\Warning('Need System permissions to add servers');
|
||||||
|
//return;
|
||||||
|
//}
|
||||||
|
|
||||||
|
require_once('includes/Report.php');
|
||||||
|
if (!empty($_REQUEST['id'])) {
|
||||||
|
$report = new ZM\Report($_REQUEST['id']);
|
||||||
|
} else {
|
||||||
|
$report = new ZM\Report();
|
||||||
|
}
|
||||||
|
global $redirect;
|
||||||
|
global $error_message;
|
||||||
|
|
||||||
|
if ($action == 'save') {
|
||||||
|
$changes = $report->changes($_REQUEST['Report']);
|
||||||
|
|
||||||
|
if (count($changes)) {
|
||||||
|
if (!$report->save($changes)) {
|
||||||
|
$error_message .= "Error saving report: " . $report->get_last_error().'</br>';
|
||||||
|
} else {
|
||||||
|
$redirect = '?view=report&id='.$report->Id();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ($action == 'delete') {
|
||||||
|
$report->delete();
|
||||||
|
$redirect = '?view=reports';
|
||||||
|
} else {
|
||||||
|
ZM\Error("Unknown action $action in saving Report");
|
||||||
|
}
|
||||||
|
?>
|
|
@ -196,7 +196,7 @@ function getAuthUser($auth) {
|
||||||
if (isset($_SESSION['username'])) {
|
if (isset($_SESSION['username'])) {
|
||||||
# In a multi-server case, we might be logged in as another user and so the auth hash didn't work
|
# In a multi-server case, we might be logged in as another user and so the auth hash didn't work
|
||||||
if (ZM_CASE_INSENSITIVE_USERNAMES) {
|
if (ZM_CASE_INSENSITIVE_USERNAMES) {
|
||||||
$sql = 'SELECT * FROM Users WHERE Enabled = 1 AND LOWER(Username) != LOWER(?)';
|
$sql = 'SELECT * FROM Users WHERE Enabled = 1 AND LOWER(Username) != LOWER(?)';
|
||||||
} else {
|
} else {
|
||||||
$sql = 'SELECT * FROM Users WHERE Enabled = 1 AND Username != ?';
|
$sql = 'SELECT * FROM Users WHERE Enabled = 1 AND Username != ?';
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ function getAuthUser($auth) {
|
||||||
} // end if
|
} // end if
|
||||||
} // end if using auth hash
|
} // end if using auth hash
|
||||||
|
|
||||||
ZM\Error("Unable to authenticate user from auth hash '$auth'");
|
ZM\Info("Unable to authenticate user from auth hash '$auth'");
|
||||||
return null;
|
return null;
|
||||||
} // end getAuthUser($auth)
|
} // end getAuthUser($auth)
|
||||||
|
|
||||||
|
|
|
@ -75,8 +75,6 @@ function expr_to_ui(expr, container) {
|
||||||
let brackets = 0;
|
let brackets = 0;
|
||||||
const used_monitorlinks = [];
|
const used_monitorlinks = [];
|
||||||
|
|
||||||
if (!tokens.length) return;
|
|
||||||
|
|
||||||
// Every monitorlink should have possible parenthesis on either side of it
|
// Every monitorlink should have possible parenthesis on either side of it
|
||||||
if (tokens.length > 3) {
|
if (tokens.length > 3) {
|
||||||
if (tokens[0].type != '(') {
|
if (tokens[0].type != '(') {
|
||||||
|
@ -160,9 +158,8 @@ function expr_to_ui(expr, container) {
|
||||||
select.append('<option value="">Add MonitorLink</option>');
|
select.append('<option value="">Add MonitorLink</option>');
|
||||||
for (monitor_id in monitors) {
|
for (monitor_id in monitors) {
|
||||||
const monitor = monitors[monitor_id];
|
const monitor = monitors[monitor_id];
|
||||||
if (!array_search(monitor.Id, used_monitorlinks)) {
|
//if (!array_search(monitor.Id, used_monitorlinks))
|
||||||
select.append('<option value="' + monitor.Id + '">' + monitor.Name + ' : All Zones</option>');
|
select.append('<option value="' + monitor.Id + '">' + monitor.Name + ' : All Zones</option>');
|
||||||
}
|
|
||||||
for ( zone_id in zones ) {
|
for ( zone_id in zones ) {
|
||||||
const zone = zones[zone_id];
|
const zone = zones[zone_id];
|
||||||
if ( monitor.Id == zone.MonitorId ) {
|
if ( monitor.Id == zone.MonitorId ) {
|
||||||
|
@ -184,8 +181,10 @@ function array_search(needle, haystack) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_to_expr() {
|
function add_to_expr() {
|
||||||
$j('[name="newMonitor[LinkedMonitors]"]').val($j('[name="newMonitor[LinkedMonitors]"]').val() + '|' + $j('#monitorLinks').val());
|
const expr = $j('[name="newMonitor[LinkedMonitors]"]');
|
||||||
expr_to_ui($j('[name="newMonitor[LinkedMonitors]"]').val(), $j('#LinkedMonitorsUI'));
|
const oldval = expr.val();
|
||||||
|
expr.val(oldval == '' ? $j('#monitorLinks').val() : oldval + '|' + $j('#monitorLinks').val());
|
||||||
|
expr_to_ui(expr.val(), $j('#LinkedMonitorsUI'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_expr(ev) {
|
function update_expr(ev) {
|
||||||
|
|
|
@ -10,23 +10,42 @@
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#alarmCues,
|
||||||
.alarmCue {
|
.alarmCue {
|
||||||
|
position: absolute;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
/*
|
||||||
background-color: #222222;
|
background-color: #222222;
|
||||||
height: 1.25em;
|
*/
|
||||||
|
height: 16px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin: 0 auto 0 auto;
|
margin: 0 auto 0 auto;
|
||||||
border-radius: 0 0 .3em .3em;
|
z-index: 10;
|
||||||
|
border: none;
|
||||||
|
border-right: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alarmCue span {
|
#alarmCues span {
|
||||||
background-color:red;
|
border-left: 1px solid black;
|
||||||
height: 100%;
|
top: 0;
|
||||||
display: inline-block;
|
position: absolute;
|
||||||
border-radius: 0;
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 0;
|
||||||
|
z-index: 11;
|
||||||
|
font-size: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.alarmCue {
|
||||||
|
background-color:red;
|
||||||
|
z-index: 9;
|
||||||
|
opacity: 0.33;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.noneCue {
|
span.noneCue {
|
||||||
background: none;
|
background: none;
|
||||||
|
z-index: 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
|
@ -150,16 +169,27 @@ height: 100%;
|
||||||
|
|
||||||
#progressBar {
|
#progressBar {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
/*
|
||||||
top: -1.25em;
|
top: -1.25em;
|
||||||
|
*/
|
||||||
height: 1.25em;
|
height: 1.25em;
|
||||||
|
/*
|
||||||
margin: 0 auto -1.25em auto;
|
margin: 0 auto -1.25em auto;
|
||||||
|
*/
|
||||||
|
margin: 0;
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#progressBar .progressBox {
|
#progressBar .progressBox {
|
||||||
transition: width .1s;
|
transition: width .1s;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
background: rgba(170, 170, 170, .7);
|
background: rgba(170, 170, 170, .7);
|
||||||
|
/*
|
||||||
border-radius: 0 0 .3em .3em;
|
border-radius: 0 0 .3em .3em;
|
||||||
|
*/
|
||||||
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#eventStills {
|
#eventStills {
|
||||||
|
@ -288,3 +318,9 @@ svg.zones {
|
||||||
#toggleZonesButton span.material-icons {
|
#toggleZonesButton span.material-icons {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
#indicator {
|
||||||
|
height: 2.75em;
|
||||||
|
position: absolute;
|
||||||
|
border-left: 1px solid blue;
|
||||||
|
margin-top: -1.25em;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
#reportsTable.major .colTime {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#reportsTable [data-field="Id"],
|
||||||
|
#reportsTable [data-field="Interval"] {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#reportsTable [data-field="Name"] {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header h2, #header a {
|
||||||
|
line-height: 1.1;
|
||||||
|
margin:5px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header #info, #header #pagination, #header #controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header #controls {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header #pagination {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dirty hack to fix up/down buttons on pagination number input */
|
||||||
|
input[type="number"].form-control {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
|
@ -120,7 +120,7 @@ echo output_link_if_exists(array(
|
||||||
'css/base/views/'.$basename.'.css',
|
'css/base/views/'.$basename.'.css',
|
||||||
'js/dateTimePicker/jquery-ui-timepicker-addon.css',
|
'js/dateTimePicker/jquery-ui-timepicker-addon.css',
|
||||||
'js/jquery-ui-1.12.1/jquery-ui.structure.min.css',
|
'js/jquery-ui-1.12.1/jquery-ui.structure.min.css',
|
||||||
));
|
), true);
|
||||||
if ( $css != 'base' )
|
if ( $css != 'base' )
|
||||||
echo output_link_if_exists(array(
|
echo output_link_if_exists(array(
|
||||||
'css/'.$css.'/skin.css',
|
'css/'.$css.'/skin.css',
|
||||||
|
@ -234,6 +234,7 @@ function getNormalNavBarHTML($running, $user, $bandwidth_options, $view, $skin)
|
||||||
echo getMontageHTML($view);
|
echo getMontageHTML($view);
|
||||||
echo getMontageReviewHTML($view);
|
echo getMontageReviewHTML($view);
|
||||||
echo getSnapshotsHTML($view);
|
echo getSnapshotsHTML($view);
|
||||||
|
echo getReportsHTML($view);
|
||||||
echo getRprtEvntAuditHTML($view);
|
echo getRprtEvntAuditHTML($view);
|
||||||
echo getHeaderFlipHTML();
|
echo getHeaderFlipHTML();
|
||||||
echo '</ul>';
|
echo '</ul>';
|
||||||
|
@ -368,6 +369,7 @@ function getCollapsedNavBarHTML($running, $user, $bandwidth_options, $view, $ski
|
||||||
echo getMontageHTML($view);
|
echo getMontageHTML($view);
|
||||||
echo getMontageReviewHTML($view);
|
echo getMontageReviewHTML($view);
|
||||||
echo getSnapshotsHTML($view);
|
echo getSnapshotsHTML($view);
|
||||||
|
echo getReportsHTML($view);
|
||||||
echo getRprtEvntAuditHTML($view);
|
echo getRprtEvntAuditHTML($view);
|
||||||
echo '</ul>';
|
echo '</ul>';
|
||||||
}
|
}
|
||||||
|
@ -771,6 +773,17 @@ function getSnapshotsHTML($view) {
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getReportsHTML($view) {
|
||||||
|
$result = '';
|
||||||
|
|
||||||
|
if (canView('Events')) {
|
||||||
|
$class = ($view == 'reports' or $view == 'report') ? ' selected' : '';
|
||||||
|
$result .= '<li id="getReportsHTML" class="nav-item dropdown"><a class="nav-link'.$class.'" href="?view=reports">'.translate('Reports').'</a></li>'.PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns the html representing the Audit Events Report menu item
|
// Returns the html representing the Audit Events Report menu item
|
||||||
function getRprtEvntAuditHTML($view) {
|
function getRprtEvntAuditHTML($view) {
|
||||||
$result = '';
|
$result = '';
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -334,7 +334,7 @@ if ( currentView != 'none' && currentView != 'login' ) {
|
||||||
function insertModalHtml(name, html) {
|
function insertModalHtml(name, html) {
|
||||||
var modal = $j('#' + name);
|
var modal = $j('#' + name);
|
||||||
|
|
||||||
if ( modal.length ) {
|
if (modal.length) {
|
||||||
modal.replaceWith(html);
|
modal.replaceWith(html);
|
||||||
} else {
|
} else {
|
||||||
$j("body").append(html);
|
$j("body").append(html);
|
||||||
|
@ -360,13 +360,13 @@ if ( currentView != 'none' && currentView != 'login' ) {
|
||||||
if (error == 'Unauthorized') {
|
if (error == 'Unauthorized') {
|
||||||
window.location.reload(true);
|
window.location.reload(true);
|
||||||
}
|
}
|
||||||
if ( ! jqxhr.responseText ) {
|
if (!jqxhr.responseText) {
|
||||||
console.log("No responseText in jqxhr");
|
console.log("No responseText in jqxhr");
|
||||||
console.log(jqxhr);
|
console.log(jqxhr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("Response Text: " + jqxhr.responseText.replace(/(<([^>]+)>)/gi, ''));
|
console.log("Response Text: " + jqxhr.responseText.replace(/(<([^>]+)>)/gi, ''));
|
||||||
if ( textStatus != "timeout" ) {
|
if (textStatus != "timeout") {
|
||||||
// The idea is that this should only fail due to auth, so reload the page
|
// The idea is that this should only fail due to auth, so reload the page
|
||||||
// which should go to login if it can't stay logged in.
|
// which should go to login if it can't stay logged in.
|
||||||
window.location.reload(true);
|
window.location.reload(true);
|
||||||
|
@ -375,29 +375,30 @@ if ( currentView != 'none' && currentView != 'login' ) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNavBar(data) {
|
function setNavBar(data) {
|
||||||
if ( !data ) {
|
if (!data) {
|
||||||
console.error("No data in setNavBar");
|
console.error("No data in setNavBar");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ( data.auth ) {
|
if (data.auth) {
|
||||||
if ( data.auth != auth_hash ) {
|
if (data.auth != auth_hash) {
|
||||||
console.log("Update auth_hash to "+data.auth);
|
console.log("Update auth_hash to "+data.auth);
|
||||||
// Update authentication token.
|
// Update authentication token.
|
||||||
auth_hash = data.auth;
|
auth_hash = data.auth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( data.auth_relay ) {
|
if (data.auth_relay) {
|
||||||
auth_relay = data.auth_relay;
|
auth_relay = data.auth_relay;
|
||||||
}
|
}
|
||||||
// iterate through all the keys then update each element id with the same name
|
// iterate through all the keys then update each element id with the same name
|
||||||
for (var key of Object.keys(data)) {
|
for (var key of Object.keys(data)) {
|
||||||
if ( key == "auth" ) continue;
|
if ( key == "auth" ) continue;
|
||||||
|
if ( key == "auth_relay" ) continue;
|
||||||
if ( $j('#'+key).hasClass("show") ) continue; // don't update if the user has the dropdown open
|
if ( $j('#'+key).hasClass("show") ) continue; // don't update if the user has the dropdown open
|
||||||
if ( $j('#'+key).length ) $j('#'+key).replaceWith(data[key]);
|
if ( $j('#'+key).length ) $j('#'+key).replaceWith(data[key]);
|
||||||
if ( key == 'getBandwidthHTML' ) bwClickFunction();
|
if ( key == 'getBandwidthHTML' ) bwClickFunction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} // end if ( currentView != 'none' && currentView != 'login' )
|
||||||
|
|
||||||
//Shows a message if there is an error in the streamObj or the stream doesn't exist. Returns true if error, false otherwise.
|
//Shows a message if there is an error in the streamObj or the stream doesn't exist. Returns true if error, false otherwise.
|
||||||
function checkStreamForErrors(funcName, streamObj) {
|
function checkStreamForErrors(funcName, streamObj) {
|
||||||
|
@ -441,7 +442,7 @@ function secsToTime( seconds ) {
|
||||||
}
|
}
|
||||||
timeString = timeHours+":"+timeMins+":"+timeSecs;
|
timeString = timeHours+":"+timeMins+":"+timeSecs;
|
||||||
}
|
}
|
||||||
return ( timeString );
|
return timeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitTab(evt) {
|
function submitTab(evt) {
|
||||||
|
@ -712,11 +713,13 @@ function getLogoutModal() {
|
||||||
.fail(logAjaxFail);
|
.fail(logAjaxFail);
|
||||||
}
|
}
|
||||||
function clickLogout() {
|
function clickLogout() {
|
||||||
if ( ! $j('#modalLogout').length ) {
|
const modalLogout = $j('#modalLogout');
|
||||||
|
|
||||||
|
if (!modalLogout.length) {
|
||||||
getLogoutModal();
|
getLogoutModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$j('#modalLogout').modal('show');
|
modalLogout.modal('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStateModal() {
|
function getStateModal() {
|
||||||
|
|
|
@ -245,9 +245,10 @@ if ( (ZM_WEB_STREAM_METHOD == 'mpeg') && ZM_MPEG_LIVE_FORMAT ) {
|
||||||
}
|
}
|
||||||
} // end if stream method
|
} // end if stream method
|
||||||
?>
|
?>
|
||||||
<div id="alarmCue" class="alarmCue"></div>
|
|
||||||
<div id="progressBar" style="width: 100%;">
|
<div id="progressBar" style="width: 100%;">
|
||||||
|
<div id="alarmCues" style="width: 100%;"></div>
|
||||||
<div class="progressBox" id="progressBox" title="" style="width: 0%;"></div>
|
<div class="progressBox" id="progressBox" title="" style="width: 0%;"></div>
|
||||||
|
<div id="indicator" style="display: none;"></div>
|
||||||
</div><!--progressBar-->
|
</div><!--progressBar-->
|
||||||
<?php
|
<?php
|
||||||
} /*end if !DefaultVideo*/
|
} /*end if !DefaultVideo*/
|
||||||
|
|
|
@ -105,18 +105,51 @@ function setAlarmCues(data) {
|
||||||
} else {
|
} else {
|
||||||
cueFrames = data.frames;
|
cueFrames = data.frames;
|
||||||
alarmSpans = renderAlarmCues(vid ? $j("#videoobj") : $j("#evtStream"));//use videojs width or zms width
|
alarmSpans = renderAlarmCues(vid ? $j("#videoobj") : $j("#evtStream"));//use videojs width or zms width
|
||||||
$j(".alarmCue").html(alarmSpans);
|
$j('#alarmCues').html(alarmSpans);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAlarmCues(containerEl) {
|
function renderAlarmCues(containerEl) {
|
||||||
if ( !( cueFrames && cueFrames.length ) ) {
|
let html = '';
|
||||||
|
|
||||||
|
/*
|
||||||
|
grid_size = 25;
|
||||||
|
const canvas = document.getElementById('alarmCues');
|
||||||
|
|
||||||
|
canvas_width = canvas.width = containerEl.width();
|
||||||
|
pixPerSegment = canvas_width / eventData.Length
|
||||||
|
console.log(pixPerSegment);
|
||||||
|
console.log(canvas);
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
for (let i=0; i <= pixPerSegment; i++) {
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.strokeStyle = "#000000";
|
||||||
|
ctx.moveTo(grid_size*i, 0);
|
||||||
|
ctx.lineTo(grid_size*i, canvas.height);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
cues_div = document.getElementById('alarmCues');
|
||||||
|
const event_length = (eventData.Length > cueFrames[cueFrames.length - 1].Delta) ? eventData.Length : cueFrames[cueFrames.length - 1].Delta;
|
||||||
|
let span_count = 10;
|
||||||
|
let span_seconds = parseInt(event_length / span_count);
|
||||||
|
let span_width = parseInt(containerEl.width() / span_count);
|
||||||
|
console.log(span_width, containerEl.width(), span_count);
|
||||||
|
//let span_width =
|
||||||
|
const date = new Date(eventData.StartDateTime);
|
||||||
|
for (let i=0; i < span_count; i += 1) {
|
||||||
|
html += '<span style="left:'+(i*span_width)+'px; width: '+span_width+'px;">'+date.toLocaleTimeString()+'</span>';
|
||||||
|
date.setTime(date.getTime() + span_seconds*1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(cueFrames && cueFrames.length)) {
|
||||||
console.log('No cue frames for event');
|
console.log('No cue frames for event');
|
||||||
return;
|
return html;
|
||||||
}
|
}
|
||||||
// This uses the Delta of the last frame to get the length of the event. I can't help but wonder though
|
// This uses the Delta of the last frame to get the length of the event. I can't help but wonder though
|
||||||
// if we shouldn't just use the event length endtime-starttime
|
// if we shouldn't just use the event length endtime-starttime
|
||||||
var cueRatio = containerEl.width() / (cueFrames[cueFrames.length - 1].Delta * 100);
|
var cueRatio = containerEl.width() / (event_length * 100);
|
||||||
var minAlarm = Math.ceil(1/cueRatio);
|
var minAlarm = Math.ceil(1/cueRatio);
|
||||||
var spanTimeStart = 0;
|
var spanTimeStart = 0;
|
||||||
var spanTimeEnd = 0;
|
var spanTimeEnd = 0;
|
||||||
|
@ -125,22 +158,28 @@ function renderAlarmCues(containerEl) {
|
||||||
var pixSkew = 0;
|
var pixSkew = 0;
|
||||||
var skip = 0;
|
var skip = 0;
|
||||||
var num_cueFrames = cueFrames.length;
|
var num_cueFrames = cueFrames.length;
|
||||||
for ( var i = 0; i < num_cueFrames; i++ ) {
|
let left = 0;
|
||||||
|
|
||||||
|
for (let i=0; i < num_cueFrames; i++) {
|
||||||
skip = 0;
|
skip = 0;
|
||||||
frame = cueFrames[i];
|
frame = cueFrames[i];
|
||||||
if ( (frame.Type == 'Alarm') && (alarmed == 0) ) { //From nothing to alarm. End nothing and start alarm.
|
|
||||||
|
if ((frame.Type == 'Alarm') && (alarmed == 0)) { //From nothing to alarm. End nothing and start alarm.
|
||||||
alarmed = 1;
|
alarmed = 1;
|
||||||
if (frame.Delta == 0) continue; //If event starts with an alarm or too few for a nonespan
|
if (frame.Delta == 0) continue; //If event starts with an alarm or too few for a nonespan
|
||||||
spanTimeEnd = frame.Delta * 100;
|
spanTimeEnd = frame.Delta * 100;
|
||||||
spanTime = spanTimeEnd - spanTimeStart;
|
spanTime = spanTimeEnd - spanTimeStart;
|
||||||
var pix = cueRatio * spanTime;
|
let pix = cueRatio * spanTime;
|
||||||
pixSkew += pix - Math.round(pix);//average out the rounding errors.
|
pixSkew += pix - Math.round(pix);//average out the rounding errors.
|
||||||
pix = Math.round(pix);
|
pix = Math.round(pix);
|
||||||
if ((pixSkew > 1 || pixSkew < -1) && pix + Math.round(pixSkew) > 0) { //add skew if it's a pixel and won't zero out span.
|
if ((pixSkew > 1 || pixSkew < -1) && pix + Math.round(pixSkew) > 0) { //add skew if it's a pixel and won't zero out span.
|
||||||
pix += Math.round(pixSkew);
|
pix += Math.round(pixSkew);
|
||||||
pixSkew = pixSkew - Math.round(pixSkew);
|
pixSkew = pixSkew - Math.round(pixSkew);
|
||||||
}
|
}
|
||||||
alarmHtml += '<span class="alarmCue noneCue" style="width: ' + pix + 'px;"></span>';
|
|
||||||
|
alarmHtml += '<span class="alarmCue noneCue" style="left: '+left+'px; width: ' + pix + 'px;"></span>';
|
||||||
|
left = parseInt((frame.Delta / event_length) * containerEl.width());
|
||||||
|
console.log(left, frame.Delta, event_length, containerEl.width());
|
||||||
spanTimeStart = spanTimeEnd;
|
spanTimeStart = spanTimeEnd;
|
||||||
} else if ( (frame.Type !== 'Alarm') && (alarmed == 1) ) { //from alarm to nothing. End alarm and start nothing.
|
} else if ( (frame.Type !== 'Alarm') && (alarmed == 1) ) { //from alarm to nothing. End alarm and start nothing.
|
||||||
futNone = 0;
|
futNone = 0;
|
||||||
|
@ -170,7 +209,8 @@ function renderAlarmCues(containerEl) {
|
||||||
pix += Math.round(pixSkew);
|
pix += Math.round(pixSkew);
|
||||||
pixSkew = pixSkew - Math.round(pixSkew);
|
pixSkew = pixSkew - Math.round(pixSkew);
|
||||||
}
|
}
|
||||||
alarmHtml += '<span class="alarmCue" style="width: ' + pix + 'px;"></span>';
|
alarmHtml += '<span class="alarmCue" style="left: '+left+'px; width: ' + pix + 'px;"></span>';
|
||||||
|
left = parseInt((frame.Delta / event_length) * containerEl.width());
|
||||||
spanTimeStart = spanTimeEnd;
|
spanTimeStart = spanTimeEnd;
|
||||||
} else if ( (frame.Type == 'Alarm') && (alarmed == 1) && (i + 1 >= cueFrames.length) ) { //event ends on an alarm
|
} else if ( (frame.Type == 'Alarm') && (alarmed == 1) && (i + 1 >= cueFrames.length) ) { //event ends on an alarm
|
||||||
spanTimeEnd = frame.Delta * 100;
|
spanTimeEnd = frame.Delta * 100;
|
||||||
|
@ -178,10 +218,11 @@ function renderAlarmCues(containerEl) {
|
||||||
alarmed = 0;
|
alarmed = 0;
|
||||||
pix = Math.round(cueRatio * spanTime);
|
pix = Math.round(cueRatio * spanTime);
|
||||||
if (pixSkew >= .5 || pixSkew <= -.5) pix += Math.round(pixSkew);
|
if (pixSkew >= .5 || pixSkew <= -.5) pix += Math.round(pixSkew);
|
||||||
alarmHtml += '<span class="alarmCue" style="width: ' + pix + 'px;"></span>';
|
|
||||||
|
alarmHtml += '<span class="alarmCue" style="left: '+left+'px; width: ' + pix + 'px;"></span>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return alarmHtml;
|
return html + alarmHtml;
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeCodec() {
|
function changeCodec() {
|
||||||
|
@ -193,8 +234,9 @@ function changeScale() {
|
||||||
var newWidth;
|
var newWidth;
|
||||||
var newHeight;
|
var newHeight;
|
||||||
var autoScale;
|
var autoScale;
|
||||||
var eventViewer = $j(vid ? '#videoobj' : '#videoFeed');
|
const eventViewer = $j(vid ? '#videoobj' : '#evtStream');
|
||||||
var alarmCue = $j('div.alarmCue');
|
|
||||||
|
var alarmCue = $j('#alarmCues');
|
||||||
var bottomEl = $j('#replayStatus');
|
var bottomEl = $j('#replayStatus');
|
||||||
|
|
||||||
if (scale == '0') {
|
if (scale == '0') {
|
||||||
|
@ -766,17 +808,57 @@ function updateProgressBar() {
|
||||||
if (!(eventData && streamStatus)) {
|
if (!(eventData && streamStatus)) {
|
||||||
return;
|
return;
|
||||||
} // end if ! eventData && streamStatus
|
} // end if ! eventData && streamStatus
|
||||||
var curWidth = (streamStatus.progress / parseFloat(eventData.Length)) * 100;
|
const curWidth = (streamStatus.progress / parseFloat(eventData.Length)) * 100;
|
||||||
$j("#progressBox").css('width', curWidth + '%');
|
|
||||||
|
const progressDate = new Date(eventData.StartDateTime);
|
||||||
|
progressDate.setTime(progressDate.getTime() + (streamStatus.progress*1000));
|
||||||
|
|
||||||
|
const progressBox = $j("#progressBox");
|
||||||
|
progressBox.css('width', curWidth + '%');
|
||||||
|
progressBox.attr('title', progressDate.toLocaleTimeString());
|
||||||
} // end function updateProgressBar()
|
} // end function updateProgressBar()
|
||||||
|
|
||||||
// Handles seeking when clicking on the progress bar.
|
// Handles seeking when clicking on the progress bar.
|
||||||
function progressBarNav() {
|
function progressBarNav() {
|
||||||
$j('#progressBar').click(function(e) {
|
$j('#progressBar').click(function(e) {
|
||||||
var x = e.pageX - $j(this).offset().left;
|
let x = e.pageX - $j(this).offset().left;
|
||||||
var seekTime = (x / $j('#progressBar').width()) * parseFloat(eventData.Length);
|
if (x<0) x=0;
|
||||||
|
const seekTime = (x / $j('#progressBar').width()) * parseFloat(eventData.Length);
|
||||||
|
console.log("clicked at ", x, seekTime);
|
||||||
streamSeek(seekTime);
|
streamSeek(seekTime);
|
||||||
});
|
});
|
||||||
|
$j('#progressBar').mouseover(function(e) {
|
||||||
|
let x = e.pageX - $j(this).offset().left;
|
||||||
|
if (x<0) x=0;
|
||||||
|
console.log(x);
|
||||||
|
const seekTime = (x / $j('#progressBar').width()) * parseFloat(eventData.Length);
|
||||||
|
const indicator = document.getElementById('indicator');
|
||||||
|
indicator.style.display = 'block';
|
||||||
|
indicator.style.left = x + 'px';
|
||||||
|
indicator.setAttribute('title', seekTime);
|
||||||
|
});
|
||||||
|
$j('#progressBar').mouseout(function(e) {
|
||||||
|
const indicator = document.getElementById('indicator');
|
||||||
|
indicator.style.display = 'none';
|
||||||
|
});
|
||||||
|
$j('#progressBar').mousemove(function(e) {
|
||||||
|
const bar = $j(this);
|
||||||
|
|
||||||
|
let x = e.pageX - bar.offset().left;
|
||||||
|
if (x<0) x=0;
|
||||||
|
if (x > bar.width()) x = bar.width();
|
||||||
|
|
||||||
|
let seekTime = (x / bar.width()) * parseFloat(eventData.Length);
|
||||||
|
|
||||||
|
const indicator = document.getElementById('indicator');
|
||||||
|
|
||||||
|
const date = new Date(eventData.StartDateTime);
|
||||||
|
date.setTime(date.getTime() + (seekTime*1000));
|
||||||
|
|
||||||
|
indicator.innerHTML = date.toLocaleTimeString();
|
||||||
|
indicator.style.left = x+'px';
|
||||||
|
indicator.setAttribute('title', seekTime);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClick(event) {
|
function handleClick(event) {
|
||||||
|
@ -932,7 +1014,7 @@ function initPage() {
|
||||||
if ($j('#videoobj').length) {
|
if ($j('#videoobj').length) {
|
||||||
vid = videojs('videoobj');
|
vid = videojs('videoobj');
|
||||||
addVideoTimingTrack(vid, LabelFormat, eventData.MonitorName, eventData.Length, eventData.StartDateTime);
|
addVideoTimingTrack(vid, LabelFormat, eventData.MonitorName, eventData.Length, eventData.StartDateTime);
|
||||||
$j('.vjs-progress-control').append('<div class="alarmCue"></div>');//add a place for videojs only on first load
|
$j('.vjs-progress-control').append('<div id="alarmCues" class="alarmCues"></div>');//add a place for videojs only on first load
|
||||||
vid.on('ended', vjsReplay);
|
vid.on('ended', vjsReplay);
|
||||||
vid.on('play', vjsPlay);
|
vid.on('play', vjsPlay);
|
||||||
vid.on('pause', pauseClicked);
|
vid.on('pause', pauseClicked);
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
var backBtn = $j('#backBtn');
|
||||||
|
var deleteBtn = $j('#deleteBtn');
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
const selections = getIdSelections();
|
||||||
|
if (!selections.length) {
|
||||||
|
alert('Please select reports to delete.');
|
||||||
|
} else {
|
||||||
|
deleteReports(selections);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manage the CANCEL modal button
|
||||||
|
document.getElementById("delCancelBtn").addEventListener('click', function onDelCancelClick(evt) {
|
||||||
|
$j('#deleteConfirm').modal('hide');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteReports(ids) {
|
||||||
|
const ticker = document.getElementById('deleteProgressTicker');
|
||||||
|
const chunk = ids.splice(0, 10);
|
||||||
|
console.log("Deleting " + chunk.length + " selections. " + ids.length);
|
||||||
|
|
||||||
|
$j.getJSON(thisUrl + '?request=reports&task=delete&ids[]='+chunk.join('&ids[]='))
|
||||||
|
.done( function(data) {
|
||||||
|
if (!ids.length) {
|
||||||
|
$j('#reportsTable').bootstrapTable('refresh');
|
||||||
|
$j('#deleteConfirm').modal('hide');
|
||||||
|
} else {
|
||||||
|
if (ticker.innerHTML.length < 1 || ticker.innerHTML.length > 10) {
|
||||||
|
ticker.innerHTML = '.';
|
||||||
|
} else {
|
||||||
|
ticker.innerHTML = ticker.innerHTML + '.';
|
||||||
|
}
|
||||||
|
deleteReports(ids);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail( function(jqxhr) {
|
||||||
|
logAjaxFail(jqxhr);
|
||||||
|
$j('#reportsTable').bootstrapTable('refresh');
|
||||||
|
$j('#deleteConfirm').modal('hide');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPage() {
|
||||||
|
// Load the delete confirmation modal into the DOM
|
||||||
|
getDelConfirmModal();
|
||||||
|
|
||||||
|
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
|
||||||
|
document.getElementById("backBtn").addEventListener('click', function onBackClick(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
window.history.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manage the DELETE button
|
||||||
|
document.getElementById("deleteBtn").addEventListener('click', function onDeleteClick(evt) {
|
||||||
|
if (!canEdit.Events) {
|
||||||
|
enoperm();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.preventDefault();
|
||||||
|
$j('#deleteConfirm').modal('show');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$j(document).ready(function() {
|
||||||
|
initPage();
|
||||||
|
});
|
|
@ -0,0 +1,188 @@
|
||||||
|
var backBtn = $j('#backBtn');
|
||||||
|
var viewBtn = $j('#viewBtn');
|
||||||
|
var editBtn = $j('#editBtn');
|
||||||
|
var deleteBtn = $j('#deleteBtn');
|
||||||
|
var table = $j('#reportsTable');
|
||||||
|
|
||||||
|
/*
|
||||||
|
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=reports&task=query', params.data)
|
||||||
|
.done(function(data) {
|
||||||
|
if (data.result == 'Error') {
|
||||||
|
alert(data.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
$j('#reportsTable').bootstrapTable('refresh');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function processRows(rows) {
|
||||||
|
$j.each(rows, function(ndx, row) {
|
||||||
|
const id = row.Id;
|
||||||
|
|
||||||
|
row.Id = '<a href="?view=report&id=' + id + '&page=1">' + id + '</a>';
|
||||||
|
row.Name = '<a href="?view=report&id=' + id + '&page=1">' + row.Name + '</a>';
|
||||||
|
});
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the event id's of the selected rows
|
||||||
|
function getIdSelections() {
|
||||||
|
var table = $j('#reportsTable');
|
||||||
|
|
||||||
|
return $j.map(table.bootstrapTable('getSelections'), function(row) {
|
||||||
|
return row.Id.replace(/(<([^>]+)>)/gi, ''); // strip the html from the element before sending
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
const selections = getIdSelections();
|
||||||
|
if (!selections.length) {
|
||||||
|
alert('Please select reports to delete.');
|
||||||
|
} else {
|
||||||
|
deleteReports(selections);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manage the CANCEL modal button
|
||||||
|
document.getElementById("delCancelBtn").addEventListener('click', function onDelCancelClick(evt) {
|
||||||
|
$j('#deleteConfirm').modal('hide');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteReports(ids) {
|
||||||
|
const ticker = document.getElementById('deleteProgressTicker');
|
||||||
|
const chunk = ids.splice(0, 10);
|
||||||
|
console.log("Deleting " + chunk.length + " selections. " + ids.length);
|
||||||
|
|
||||||
|
$j.getJSON(thisUrl + '?request=reports&task=delete&ids[]='+chunk.join('&ids[]='))
|
||||||
|
.done( function(data) {
|
||||||
|
if (!ids.length) {
|
||||||
|
$j('#reportsTable').bootstrapTable('refresh');
|
||||||
|
$j('#deleteConfirm').modal('hide');
|
||||||
|
} else {
|
||||||
|
if (ticker.innerHTML.length < 1 || ticker.innerHTML.length > 10) {
|
||||||
|
ticker.innerHTML = '.';
|
||||||
|
} else {
|
||||||
|
ticker.innerHTML = ticker.innerHTML + '.';
|
||||||
|
}
|
||||||
|
deleteReports(ids);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail( function(jqxhr) {
|
||||||
|
logAjaxFail(jqxhr);
|
||||||
|
$j('#reportsTable').bootstrapTable('refresh');
|
||||||
|
$j('#deleteConfirm').modal('hide');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPage() {
|
||||||
|
// Load the delete confirmation modal into the DOM
|
||||||
|
getDelConfirmModal();
|
||||||
|
|
||||||
|
// Init the bootstrap-table
|
||||||
|
table.bootstrapTable({icons: icons});
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("newBtn").addEventListener('click', function (evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
window.location = '?view=report';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manage the DELETE button
|
||||||
|
document.getElementById("deleteBtn").addEventListener('click', function onDeleteClick(evt) {
|
||||||
|
if (!canEdit.Events) {
|
||||||
|
enoperm();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.preventDefault();
|
||||||
|
$j('#deleteConfirm').modal('show');
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
|
@ -344,6 +344,7 @@ function probeNetwork() {
|
||||||
|
|
||||||
$macBases = array(
|
$macBases = array(
|
||||||
'00:0f:7c' => array('type'=>'ACTi','probeFunc'=>'probeACTi'),
|
'00:0f:7c' => array('type'=>'ACTi','probeFunc'=>'probeACTi'),
|
||||||
|
#'9c:8e:cd' => array('type'=>'Amcrest', 'probeFunc'=>'probeAmcrest'),
|
||||||
'00:40:8c' => array('type'=>'Axis', 'probeFunc'=>'probeAxis'),
|
'00:40:8c' => array('type'=>'Axis', 'probeFunc'=>'probeAxis'),
|
||||||
'2c:a5:9c' => array('type'=>'Hikvision', 'probeFunc'=>'probeHikvision'),
|
'2c:a5:9c' => array('type'=>'Hikvision', 'probeFunc'=>'probeHikvision'),
|
||||||
'00:80:f0' => array('type'=>'Panasonic','probeFunc'=>'probePana'),
|
'00:80:f0' => array('type'=>'Panasonic','probeFunc'=>'probePana'),
|
||||||
|
|
|
@ -0,0 +1,183 @@
|
||||||
|
<?php
|
||||||
|
//
|
||||||
|
// ZoneMinder web reports view file
|
||||||
|
// Copyright (C) 2022 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('Reports')) {
|
||||||
|
#$view = 'error';
|
||||||
|
#return;
|
||||||
|
#} else if (!ZM_FEATURES_SNAPSHOTS) {
|
||||||
|
#$view = 'console';
|
||||||
|
#return;
|
||||||
|
#}
|
||||||
|
#
|
||||||
|
|
||||||
|
require_once('includes/Event.php');
|
||||||
|
require_once('includes/Filter.php');
|
||||||
|
require_once('includes/Report.php');
|
||||||
|
|
||||||
|
$report_id = isset($_REQUEST['id']) ? validInt($_REQUEST['id']) : '';
|
||||||
|
$report = new ZM\Report($report_id);
|
||||||
|
|
||||||
|
xhtmlHeaders(__FILE__, translate('Reports'));
|
||||||
|
getBodyTopHTML();
|
||||||
|
echo getNavBarHTML();
|
||||||
|
?>
|
||||||
|
<div id="page" class="container-fluid p-3">
|
||||||
|
<div class="Edit">
|
||||||
|
<form name="report" id="reportForm" method="post" action="?view=report&id=<?php echo $report_id ?>">
|
||||||
|
<!-- Toolbar button placement and styling handled by bootstrap-tables -->
|
||||||
|
<div id="toolbar">
|
||||||
|
<button id="backBtn" type="button" 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="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="saveBtn" name="action" value="save" type="submit" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Save') ?>"><i class="fa fa-save"></i></button>
|
||||||
|
<button id="deleteBtn" name="action" value="delete" type="submit" 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 class="major table table-sm">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th class="text-right" scope="row"><?php echo translate('Name') ?></th>
|
||||||
|
<td><input type="text" name="Report[Name]" value="<?php echo $report->Name() ?>"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="text-right " scope="row"><?php echo translate('Filter') ?></th>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
$FilterById = array();
|
||||||
|
foreach (ZM\Filter::find() as $F) {
|
||||||
|
$FiltersById[$F->Id()] = $F;
|
||||||
|
}
|
||||||
|
echo htmlSelect('Report[FilterId]', array(''=>translate('select')) + $FiltersById, $report->FilterId())
|
||||||
|
?></td>
|
||||||
|
</tr>
|
||||||
|
<!--
|
||||||
|
<tr>
|
||||||
|
<th class="text-right" scope="row"><?php echo translate('Starting') ?></th>
|
||||||
|
<td><input type="text" name="Report[StartDateTime]" value="<?php echo $report->StartDateTime() ?>"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="text-right" scope="row"><?php echo translate('Ending') ?></th>
|
||||||
|
<td><input type="text" name="Report[EndDateTime]" value="<?php echo $report->EndDateTime() ?>"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th class="text-right" scope="row"><?php echo translate('Interval') ?></th>
|
||||||
|
<td><input type="text" name="Report[Interval]" value="<?php echo $report->Interval() ?>"/></td>
|
||||||
|
</tr>
|
||||||
|
-->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<canvas id="bar-chart" width=300" height="150"></canvas>
|
||||||
|
|
||||||
|
<script src="/skins/classic/js/Chart.min.js"></script>
|
||||||
|
<script nonce="<?php echo $cspNonce; ?>">
|
||||||
|
var events = Array();
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once('includes/Filter.php');
|
||||||
|
if (!$report->FilterId()) return;
|
||||||
|
|
||||||
|
$filter = new ZM\Filter($report->FilterId());
|
||||||
|
if ($user['MonitorIds']) {
|
||||||
|
$filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds']));
|
||||||
|
}
|
||||||
|
$events = $filter->Events();
|
||||||
|
foreach ($events as $event) {
|
||||||
|
echo 'events[events.length] = '.$event->to_json().PHP_EOL;
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
time_labels = Array();
|
||||||
|
datasets = Array();
|
||||||
|
dataset_indexes = {}; // Associative array from a date String like July 20 to an index into the datasets.
|
||||||
|
for (i=0; i < 24; i++) {
|
||||||
|
time_labels[time_labels.length] = `${i}:00`;
|
||||||
|
}
|
||||||
|
months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
|
||||||
|
|
||||||
|
for (event_index=0; event_index < events.length; event_index++) {
|
||||||
|
const event = events[event_index];
|
||||||
|
const event_start = new Date(event.StartDateTime);
|
||||||
|
const day = event_start.getDate();
|
||||||
|
const date_key = months[event_start.getMonth()] + ' ' + day;
|
||||||
|
if (! (date_key in dataset_indexes)) {
|
||||||
|
dataset_indexes[date_key] = datasets.length;
|
||||||
|
}
|
||||||
|
const dataset_index = dataset_indexes[date_key];
|
||||||
|
|
||||||
|
if (!(dataset_index in datasets)) {
|
||||||
|
datasets[dataset_index] = {
|
||||||
|
label: date_key,
|
||||||
|
fill: false,
|
||||||
|
borderColor: 'rgb('+parseInt(255*Math.random())+', '+parseInt(255*Math.random())+', '+parseInt(255*Math.random())+')',
|
||||||
|
tension: 0.1,
|
||||||
|
data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
datasets[dataset_index].data[event_start.getHours()] += parseFloat(event.Length);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
for (i=0; i < datasets.length; i++) {
|
||||||
|
if (!datasets[i]) {
|
||||||
|
datasets[i] = {
|
||||||
|
label: '',
|
||||||
|
fill: false,
|
||||||
|
borderColor: 'rgb(192, 192, 192)',
|
||||||
|
tension: 0.1,
|
||||||
|
data: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
console.log(datasets);
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
labels: time_labels,
|
||||||
|
datasets: datasets,
|
||||||
|
};
|
||||||
|
|
||||||
|
new Chart(document.getElementById("bar-chart"), {
|
||||||
|
type: 'line',
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
options: {
|
||||||
|
legend: { display: false },
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: report.Name
|
||||||
|
},
|
||||||
|
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero:true
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
<?php xhtmlFooter() ?>
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
//
|
||||||
|
// ZoneMinder web reports view file
|
||||||
|
// Copyright (C) 2022 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('Reports')) {
|
||||||
|
#$view = 'error';
|
||||||
|
#return;
|
||||||
|
#} else if (!ZM_FEATURES_SNAPSHOTS) {
|
||||||
|
#$view = 'console';
|
||||||
|
#return;
|
||||||
|
#}
|
||||||
|
|
||||||
|
require_once('includes/Event.php');
|
||||||
|
require_once('includes/Filter.php');
|
||||||
|
require_once('includes/Report.php');
|
||||||
|
|
||||||
|
xhtmlHeaders(__FILE__, translate('Reports'));
|
||||||
|
getBodyTopHTML();
|
||||||
|
echo getNavBarHTML();
|
||||||
|
?>
|
||||||
|
<div id="page" class="container-fluid p-3">
|
||||||
|
<!-- Toolbar button placement and styling handled by bootstrap-tables -->
|
||||||
|
<div id="toolbar">
|
||||||
|
<button type="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 type="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 type="button" id="newBtn" class="btn btn-normal" value="AddNew" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Add New Report') ?>"><i class="fa fa-plus"></i></button>
|
||||||
|
<button type="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="reportsTable"
|
||||||
|
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="zmReportsTable"
|
||||||
|
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('Name') ?></th>
|
||||||
|
<th data-sortable="false" data-field="Description"><?php echo translate('Description') ?></th>
|
||||||
|
<th data-sortable="true" data-field="StartDateTime"><?php echo translate('Starting') ?></th>
|
||||||
|
<th data-sortable="true" data-field="EndDateTime"><?php echo translate('Ending') ?></th>
|
||||||
|
<th data-sortable="true" data-field="Interval"><?php echo translate('Interval') ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<!-- Row data populated via Ajax -->
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php xhtmlFooter() ?>
|
Loading…
Reference in New Issue