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(
|
||||
(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`"
|
||||
));
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE table_name = 'Reports'
|
||||
AND table_schema = DATABASE()
|
||||
) > 0,
|
||||
"SELECT 'Reports table exists'",
|
||||
"
|
||||
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;
|
||||
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);
|
||||
if (!$valid) {
|
||||
Error("Shared data not valid for monitor $$monitor{Id}");
|
||||
Debug(1, "Shared data not valid for monitor $$monitor{Id}");
|
||||
return undef;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -87,7 +87,7 @@ sub zmMemAttach {
|
|||
|
||||
my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id};
|
||||
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;
|
||||
}
|
||||
my $mmap_file_size = -s $mmap_file;
|
||||
|
|
|
@ -1070,6 +1070,7 @@ void EventStream::runStream() {
|
|||
);
|
||||
} else {
|
||||
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
|
||||
} else {
|
||||
// Paused
|
||||
|
|
|
@ -345,7 +345,6 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
|
|||
++it;
|
||||
} // end while
|
||||
|
||||
|
||||
Debug(1, "Resulting it pointing at latest packet? %d, next front points to begin? %d, Keyframe interval %d",
|
||||
( *it == add_packet ),
|
||||
( next_front == pktQueue.begin() ),
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
//
|
||||
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-content">
|
||||
<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'])) {
|
||||
# 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) {
|
||||
$sql = 'SELECT * FROM Users WHERE Enabled = 1 AND LOWER(Username) != LOWER(?)';
|
||||
$sql = 'SELECT * FROM Users WHERE Enabled = 1 AND LOWER(Username) != LOWER(?)';
|
||||
} else {
|
||||
$sql = 'SELECT * FROM Users WHERE Enabled = 1 AND Username != ?';
|
||||
}
|
||||
|
@ -216,7 +216,7 @@ function getAuthUser($auth) {
|
|||
} // end if
|
||||
} // 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;
|
||||
} // end getAuthUser($auth)
|
||||
|
||||
|
|
|
@ -75,8 +75,6 @@ function expr_to_ui(expr, container) {
|
|||
let brackets = 0;
|
||||
const used_monitorlinks = [];
|
||||
|
||||
if (!tokens.length) return;
|
||||
|
||||
// Every monitorlink should have possible parenthesis on either side of it
|
||||
if (tokens.length > 3) {
|
||||
if (tokens[0].type != '(') {
|
||||
|
@ -160,9 +158,8 @@ function expr_to_ui(expr, container) {
|
|||
select.append('<option value="">Add MonitorLink</option>');
|
||||
for (monitor_id in monitors) {
|
||||
const monitor = monitors[monitor_id];
|
||||
if (!array_search(monitor.Id, used_monitorlinks)) {
|
||||
select.append('<option value="' + monitor.Id + '">' + monitor.Name + ' : All Zones</option>');
|
||||
}
|
||||
//if (!array_search(monitor.Id, used_monitorlinks))
|
||||
select.append('<option value="' + monitor.Id + '">' + monitor.Name + ' : All Zones</option>');
|
||||
for ( zone_id in zones ) {
|
||||
const zone = zones[zone_id];
|
||||
if ( monitor.Id == zone.MonitorId ) {
|
||||
|
@ -184,8 +181,10 @@ function array_search(needle, haystack) {
|
|||
}
|
||||
|
||||
function add_to_expr() {
|
||||
$j('[name="newMonitor[LinkedMonitors]"]').val($j('[name="newMonitor[LinkedMonitors]"]').val() + '|' + $j('#monitorLinks').val());
|
||||
expr_to_ui($j('[name="newMonitor[LinkedMonitors]"]').val(), $j('#LinkedMonitorsUI'));
|
||||
const expr = $j('[name="newMonitor[LinkedMonitors]"]');
|
||||
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) {
|
||||
|
|
|
@ -10,23 +10,42 @@
|
|||
display: inline-block;
|
||||
}
|
||||
|
||||
#alarmCues,
|
||||
.alarmCue {
|
||||
position: absolute;
|
||||
background: none;
|
||||
|
||||
/*
|
||||
background-color: #222222;
|
||||
height: 1.25em;
|
||||
*/
|
||||
height: 16px;
|
||||
text-align: left;
|
||||
margin: 0 auto 0 auto;
|
||||
border-radius: 0 0 .3em .3em;
|
||||
z-index: 10;
|
||||
border: none;
|
||||
border-right: 1px solid black;
|
||||
}
|
||||
|
||||
.alarmCue span {
|
||||
background-color:red;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
border-radius: 0;
|
||||
#alarmCues span {
|
||||
border-left: 1px solid black;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
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 {
|
||||
background: none;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
#header {
|
||||
|
@ -150,16 +169,27 @@ height: 100%;
|
|||
|
||||
#progressBar {
|
||||
position: relative;
|
||||
/*
|
||||
top: -1.25em;
|
||||
*/
|
||||
height: 1.25em;
|
||||
/*
|
||||
margin: 0 auto -1.25em auto;
|
||||
*/
|
||||
margin: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#progressBar .progressBox {
|
||||
transition: width .1s;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
background: rgba(170, 170, 170, .7);
|
||||
/*
|
||||
border-radius: 0 0 .3em .3em;
|
||||
*/
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#eventStills {
|
||||
|
@ -288,3 +318,9 @@ svg.zones {
|
|||
#toggleZonesButton span.material-icons {
|
||||
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',
|
||||
'js/dateTimePicker/jquery-ui-timepicker-addon.css',
|
||||
'js/jquery-ui-1.12.1/jquery-ui.structure.min.css',
|
||||
));
|
||||
), true);
|
||||
if ( $css != 'base' )
|
||||
echo output_link_if_exists(array(
|
||||
'css/'.$css.'/skin.css',
|
||||
|
@ -234,6 +234,7 @@ function getNormalNavBarHTML($running, $user, $bandwidth_options, $view, $skin)
|
|||
echo getMontageHTML($view);
|
||||
echo getMontageReviewHTML($view);
|
||||
echo getSnapshotsHTML($view);
|
||||
echo getReportsHTML($view);
|
||||
echo getRprtEvntAuditHTML($view);
|
||||
echo getHeaderFlipHTML();
|
||||
echo '</ul>';
|
||||
|
@ -368,6 +369,7 @@ function getCollapsedNavBarHTML($running, $user, $bandwidth_options, $view, $ski
|
|||
echo getMontageHTML($view);
|
||||
echo getMontageReviewHTML($view);
|
||||
echo getSnapshotsHTML($view);
|
||||
echo getReportsHTML($view);
|
||||
echo getRprtEvntAuditHTML($view);
|
||||
echo '</ul>';
|
||||
}
|
||||
|
@ -771,6 +773,17 @@ function getSnapshotsHTML($view) {
|
|||
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
|
||||
function getRprtEvntAuditHTML($view) {
|
||||
$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) {
|
||||
var modal = $j('#' + name);
|
||||
|
||||
if ( modal.length ) {
|
||||
if (modal.length) {
|
||||
modal.replaceWith(html);
|
||||
} else {
|
||||
$j("body").append(html);
|
||||
|
@ -360,13 +360,13 @@ if ( currentView != 'none' && currentView != 'login' ) {
|
|||
if (error == 'Unauthorized') {
|
||||
window.location.reload(true);
|
||||
}
|
||||
if ( ! jqxhr.responseText ) {
|
||||
if (!jqxhr.responseText) {
|
||||
console.log("No responseText in jqxhr");
|
||||
console.log(jqxhr);
|
||||
return;
|
||||
}
|
||||
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
|
||||
// which should go to login if it can't stay logged in.
|
||||
window.location.reload(true);
|
||||
|
@ -375,29 +375,30 @@ if ( currentView != 'none' && currentView != 'login' ) {
|
|||
}
|
||||
|
||||
function setNavBar(data) {
|
||||
if ( !data ) {
|
||||
if (!data) {
|
||||
console.error("No data in setNavBar");
|
||||
return;
|
||||
}
|
||||
if ( data.auth ) {
|
||||
if ( data.auth != auth_hash ) {
|
||||
if (data.auth) {
|
||||
if (data.auth != auth_hash) {
|
||||
console.log("Update auth_hash to "+data.auth);
|
||||
// Update authentication token.
|
||||
auth_hash = data.auth;
|
||||
}
|
||||
}
|
||||
if ( data.auth_relay ) {
|
||||
if (data.auth_relay) {
|
||||
auth_relay = data.auth_relay;
|
||||
}
|
||||
// iterate through all the keys then update each element id with the same name
|
||||
for (var key of Object.keys(data)) {
|
||||
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).length ) $j('#'+key).replaceWith(data[key]);
|
||||
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.
|
||||
function checkStreamForErrors(funcName, streamObj) {
|
||||
|
@ -441,7 +442,7 @@ function secsToTime( seconds ) {
|
|||
}
|
||||
timeString = timeHours+":"+timeMins+":"+timeSecs;
|
||||
}
|
||||
return ( timeString );
|
||||
return timeString;
|
||||
}
|
||||
|
||||
function submitTab(evt) {
|
||||
|
@ -712,11 +713,13 @@ function getLogoutModal() {
|
|||
.fail(logAjaxFail);
|
||||
}
|
||||
function clickLogout() {
|
||||
if ( ! $j('#modalLogout').length ) {
|
||||
const modalLogout = $j('#modalLogout');
|
||||
|
||||
if (!modalLogout.length) {
|
||||
getLogoutModal();
|
||||
return;
|
||||
}
|
||||
$j('#modalLogout').modal('show');
|
||||
modalLogout.modal('show');
|
||||
}
|
||||
|
||||
function getStateModal() {
|
||||
|
|
|
@ -245,9 +245,10 @@ if ( (ZM_WEB_STREAM_METHOD == 'mpeg') && ZM_MPEG_LIVE_FORMAT ) {
|
|||
}
|
||||
} // end if stream method
|
||||
?>
|
||||
<div id="alarmCue" class="alarmCue"></div>
|
||||
<div id="progressBar" style="width: 100%;">
|
||||
<div id="alarmCues" style="width: 100%;"></div>
|
||||
<div class="progressBox" id="progressBox" title="" style="width: 0%;"></div>
|
||||
<div id="indicator" style="display: none;"></div>
|
||||
</div><!--progressBar-->
|
||||
<?php
|
||||
} /*end if !DefaultVideo*/
|
||||
|
|
|
@ -105,18 +105,51 @@ function setAlarmCues(data) {
|
|||
} else {
|
||||
cueFrames = data.frames;
|
||||
alarmSpans = renderAlarmCues(vid ? $j("#videoobj") : $j("#evtStream"));//use videojs width or zms width
|
||||
$j(".alarmCue").html(alarmSpans);
|
||||
$j('#alarmCues').html(alarmSpans);
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
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
|
||||
// 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 spanTimeStart = 0;
|
||||
var spanTimeEnd = 0;
|
||||
|
@ -125,22 +158,28 @@ function renderAlarmCues(containerEl) {
|
|||
var pixSkew = 0;
|
||||
var skip = 0;
|
||||
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;
|
||||
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;
|
||||
if (frame.Delta == 0) continue; //If event starts with an alarm or too few for a nonespan
|
||||
spanTimeEnd = frame.Delta * 100;
|
||||
spanTime = spanTimeEnd - spanTimeStart;
|
||||
var pix = cueRatio * spanTime;
|
||||
let pix = cueRatio * spanTime;
|
||||
pixSkew += pix - Math.round(pix);//average out the rounding errors.
|
||||
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.
|
||||
pix += 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;
|
||||
} else if ( (frame.Type !== 'Alarm') && (alarmed == 1) ) { //from alarm to nothing. End alarm and start nothing.
|
||||
futNone = 0;
|
||||
|
@ -170,7 +209,8 @@ function renderAlarmCues(containerEl) {
|
|||
pix += 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;
|
||||
} else if ( (frame.Type == 'Alarm') && (alarmed == 1) && (i + 1 >= cueFrames.length) ) { //event ends on an alarm
|
||||
spanTimeEnd = frame.Delta * 100;
|
||||
|
@ -178,10 +218,11 @@ function renderAlarmCues(containerEl) {
|
|||
alarmed = 0;
|
||||
pix = Math.round(cueRatio * spanTime);
|
||||
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() {
|
||||
|
@ -193,8 +234,9 @@ function changeScale() {
|
|||
var newWidth;
|
||||
var newHeight;
|
||||
var autoScale;
|
||||
var eventViewer = $j(vid ? '#videoobj' : '#videoFeed');
|
||||
var alarmCue = $j('div.alarmCue');
|
||||
const eventViewer = $j(vid ? '#videoobj' : '#evtStream');
|
||||
|
||||
var alarmCue = $j('#alarmCues');
|
||||
var bottomEl = $j('#replayStatus');
|
||||
|
||||
if (scale == '0') {
|
||||
|
@ -766,17 +808,57 @@ function updateProgressBar() {
|
|||
if (!(eventData && streamStatus)) {
|
||||
return;
|
||||
} // end if ! eventData && streamStatus
|
||||
var curWidth = (streamStatus.progress / parseFloat(eventData.Length)) * 100;
|
||||
$j("#progressBox").css('width', curWidth + '%');
|
||||
const curWidth = (streamStatus.progress / parseFloat(eventData.Length)) * 100;
|
||||
|
||||
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()
|
||||
|
||||
// Handles seeking when clicking on the progress bar.
|
||||
function progressBarNav() {
|
||||
$j('#progressBar').click(function(e) {
|
||||
var x = e.pageX - $j(this).offset().left;
|
||||
var seekTime = (x / $j('#progressBar').width()) * parseFloat(eventData.Length);
|
||||
let x = e.pageX - $j(this).offset().left;
|
||||
if (x<0) x=0;
|
||||
const seekTime = (x / $j('#progressBar').width()) * parseFloat(eventData.Length);
|
||||
console.log("clicked at ", x, 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) {
|
||||
|
@ -932,7 +1014,7 @@ function initPage() {
|
|||
if ($j('#videoobj').length) {
|
||||
vid = videojs('videoobj');
|
||||
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('play', vjsPlay);
|
||||
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(
|
||||
'00:0f:7c' => array('type'=>'ACTi','probeFunc'=>'probeACTi'),
|
||||
#'9c:8e:cd' => array('type'=>'Amcrest', 'probeFunc'=>'probeAmcrest'),
|
||||
'00:40:8c' => array('type'=>'Axis', 'probeFunc'=>'probeAxis'),
|
||||
'2c:a5:9c' => array('type'=>'Hikvision', 'probeFunc'=>'probeHikvision'),
|
||||
'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