mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2025-12-23 22:37:53 -05:00
Merge branch 'master' into add_go2rtc
This commit is contained in:
@@ -46,7 +46,7 @@ BuildRequires: polkit-devel
|
||||
BuildRequires: cmake
|
||||
BuildRequires: gnutls-devel
|
||||
BuildRequires: bzip2-devel
|
||||
BuildRequires: pcre-devel
|
||||
BuildRequires: pcre2-devel
|
||||
BuildRequires: libjpeg-turbo-devel
|
||||
BuildRequires: findutils
|
||||
BuildRequires: coreutils
|
||||
@@ -246,6 +246,9 @@ ln -s ../../../../../../../..%{_sysconfdir}/pki/tls/certs/ca-bundle.crt %{buildr
|
||||
# Handle the polkit file differently for web server agnostic support (see post)
|
||||
rm -f %{buildroot}%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
|
||||
|
||||
%check
|
||||
# Nothing to do. No tests exist.
|
||||
|
||||
%post common
|
||||
# Initial installation
|
||||
if [ $1 -eq 1 ] ; then
|
||||
|
||||
@@ -1639,6 +1639,13 @@ our @options = (
|
||||
},
|
||||
category => 'web',
|
||||
},
|
||||
{
|
||||
name => 'ZM_WEB_NAVBAR_LINKS',
|
||||
default => '',
|
||||
description => 'Additional Links to put in navbar. Should be a comma separated list of HTML a tags.',
|
||||
type => $types{string},
|
||||
category => 'web',
|
||||
},
|
||||
{
|
||||
name => 'ZM_WEB_NAVBAR_STICKY',
|
||||
default => '1',
|
||||
|
||||
@@ -1041,7 +1041,18 @@ sub patchDB {
|
||||
|
||||
sub migratePasswords {
|
||||
use Crypt::Eksblowfish::Bcrypt;
|
||||
use Data::Entropy::Algorithms qw(rand_bits);
|
||||
my $random;
|
||||
eval {
|
||||
require Bytes::Random::Secure;
|
||||
$random = Bytes::Random::Secure->new( Bits => 16*8);
|
||||
};
|
||||
if ($@ or !$random) {
|
||||
eval {
|
||||
require Data::Entropy::Algorithms;
|
||||
$random =Data::Entropy::Algorithms::rand_bits(16*8);
|
||||
};
|
||||
}
|
||||
|
||||
print("Migratings passwords, if any...\n");
|
||||
my $sql = 'SELECT * FROM `Users`';
|
||||
my $sth = $dbh->prepare_cached($sql) or die( "Can't prepare '$sql': ".$dbh->errstr() );
|
||||
@@ -1050,7 +1061,7 @@ sub migratePasswords {
|
||||
my $scheme = substr($user->{Password}, 0, 1);
|
||||
if ($scheme eq '*') {
|
||||
print('-->'.$user->{Username}." password will be migrated\n");
|
||||
my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8));
|
||||
my $salt = Crypt::Eksblowfish::Bcrypt::en_base64($random);
|
||||
my $settings = '$2a$10$'.$salt;
|
||||
my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings);
|
||||
my $new_pass_hash = '-ZM-'.$pass_hash;
|
||||
|
||||
@@ -231,14 +231,16 @@ int zmDbDoInsert(const std::string &query) {
|
||||
return 0;
|
||||
int rc;
|
||||
while ((rc = mysql_query(&dbconn, query.c_str())) and !zm_terminate) {
|
||||
std::string reason = mysql_error(&dbconn);
|
||||
if (mysql_ping(&dbconn)) {
|
||||
if (!zmDbReconnect()) sleep(1);
|
||||
} else {
|
||||
Error("Can't run query %s: %s", query.c_str(), mysql_error(&dbconn));
|
||||
Error("Can't run query %s: %d %s", query.c_str(), rc, reason.c_str());
|
||||
if ((mysql_errno(&dbconn) != ER_LOCK_WAIT_TIMEOUT))
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Might not be an int... FIXME
|
||||
int id = mysql_insert_id(&dbconn);
|
||||
Debug(2, "Success running sql insert %s. Resulting id is %d", query.c_str(), id);
|
||||
return id;
|
||||
|
||||
@@ -306,7 +306,6 @@ bool EventStream::loadEventData(uint64_t event_id) {
|
||||
last_timestamp = event_data->start_time;
|
||||
event_data->frame_count ++;
|
||||
} else {
|
||||
Debug(1, "EIther no endtime or no duration, frame_count %d, last_id %d", event_data->frame_count, last_id);
|
||||
delta = std::chrono::duration_cast<Microseconds>((event_data->end_time - last_timestamp)/(event_data->frame_count-last_id));
|
||||
Debug(1, "Setting delta from endtime %f - %f / %d - %d",
|
||||
FPSeconds(event_data->end_time.time_since_epoch()).count(),
|
||||
|
||||
@@ -555,6 +555,12 @@ int FfmpegCamera::OpenFfmpeg() {
|
||||
|
||||
if (!mOptions.empty()) {
|
||||
ret = av_dict_parse_string(&opts, mOptions.c_str(), "=", ",", 0);
|
||||
const AVDictionaryEntry *entry = av_dict_get(opts, "thread_count", nullptr, AV_DICT_MATCH_CASE);
|
||||
if (entry) {
|
||||
mVideoCodecContext->thread_count = std::stoul(entry->value);
|
||||
Debug(1, "Setting codec thread_count to %d", mVideoCodecContext->thread_count);
|
||||
av_dict_set(&opts, "thread_count", nullptr, AV_DICT_MATCH_CASE);
|
||||
}
|
||||
// reorder_queue is for avformat not codec
|
||||
av_dict_set(&opts, "reorder_queue_size", nullptr, AV_DICT_MATCH_CASE);
|
||||
av_dict_set(&opts, "probesize", nullptr, AV_DICT_MATCH_CASE);
|
||||
|
||||
@@ -1432,13 +1432,13 @@ void Monitor::CancelForced() {
|
||||
void Monitor::actionReload() { shared_data->action |= RELOAD; }
|
||||
|
||||
void Monitor::actionEnable() {
|
||||
shared_data->action |= RELOAD;
|
||||
shared_data->capturing = true;
|
||||
Info("actionEnable, capturing enabled.");
|
||||
}
|
||||
|
||||
void Monitor::actionDisable() {
|
||||
shared_data->action |= RELOAD;
|
||||
shared_data->capturing = false;
|
||||
Info("actionDisable, capturing temporarily disabled.");
|
||||
}
|
||||
|
||||
void Monitor::actionSuspend() { shared_data->action |= SUSPEND; }
|
||||
@@ -2745,6 +2745,10 @@ std::vector<std::shared_ptr<Monitor>> Monitor::LoadFfmpegMonitors(
|
||||
* Returns -1 on failure.
|
||||
*/
|
||||
int Monitor::Capture() {
|
||||
if (!shared_data->capturing) {
|
||||
Debug(1, "Not capturing");
|
||||
return 0;
|
||||
}
|
||||
unsigned int index = shared_data->image_count % image_buffer_count;
|
||||
if (image_buffer.empty() or (index >= image_buffer.size())) {
|
||||
Error("Image Buffer is invalid. Check ImageBufferCount. size is %zu",
|
||||
@@ -3487,7 +3491,7 @@ bool Monitor::DumpSettings(char *output, bool verbose) {
|
||||
alarm_ref_blend_perc);
|
||||
sprintf(output + strlen(output), "Track Motion : %d\n", track_motion);
|
||||
sprintf(output + strlen(output), "Capturing %d - %s\n", capturing,
|
||||
Capturing_Strings[capturing].c_str());
|
||||
Capturing_Strings[shared_data->capturing].c_str());
|
||||
sprintf(output + strlen(output), "Analysing %d - %s\n", analysing,
|
||||
Analysing_Strings[analysing].c_str());
|
||||
sprintf(output + strlen(output), "Recording %d - %s\n", recording,
|
||||
|
||||
@@ -105,7 +105,14 @@ void Monitor::ONVIF::start() {
|
||||
|
||||
if (rc != SOAP_OK) {
|
||||
const char *detail = soap_fault_detail(soap);
|
||||
Error("ONVIF Couldn't create subscription! %d, fault:%s, detail:%s", rc, soap_fault_string(soap), detail ? detail : "null");
|
||||
if (rc > 8) {
|
||||
Error("ONVIF Couldn't create subscription at %s! %d, fault:%s, detail:%s", full_url.c_str(),
|
||||
rc, soap_fault_string(soap), detail ? detail : "null");
|
||||
} else {
|
||||
Error("ONVIF Couldn't create subscription at %s! %d %s, fault:%s, detail:%s", full_url.c_str(),
|
||||
rc, SOAP_STRINGS[rc].c_str(),
|
||||
soap_fault_string(soap), detail ? detail : "null");
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
std::ostream *old_stream = soap->os;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "zm_time.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <ctime>
|
||||
|
||||
std::string SystemTimePointToString(SystemTimePoint tp) {
|
||||
time_t tp_sec = std::chrono::system_clock::to_time_t(tp);
|
||||
@@ -51,3 +52,11 @@ std::string TimePointToString(TimePoint tp) {
|
||||
snprintf(timePtr, timeString.capacity() - (timePtr - timeString.data()), ".%06" PRIi64, static_cast<int64_t>(now_frac.count()));
|
||||
return timeString;
|
||||
}
|
||||
|
||||
SystemTimePoint StringToSystemTimePoint(const std::string ×tamp) {
|
||||
std::tm t{};
|
||||
strptime(timestamp.c_str(), "%Y-%m-%d %H:%M:%S", &t);
|
||||
time_t time_t_val = mktime(&t);
|
||||
SystemTimePoint stp = std::chrono::system_clock::from_time_t(time_t_val);
|
||||
return stp;
|
||||
}
|
||||
|
||||
@@ -123,5 +123,6 @@ class TimeSegmentAdder {
|
||||
|
||||
std::string SystemTimePointToString(SystemTimePoint tp);
|
||||
std::string TimePointToString(TimePoint tp);
|
||||
SystemTimePoint StringToSystemTimePoint(const std::string &stp);
|
||||
|
||||
#endif // ZM_TIME_H
|
||||
|
||||
@@ -1171,20 +1171,8 @@ int VideoStore::writeVideoFramePacket(const std::shared_ptr<ZMPacket> zm_packet)
|
||||
video_out_ctx->height
|
||||
);
|
||||
} else if (!zm_packet->in_frame) {
|
||||
Debug(4, "Have no in_frame");
|
||||
if (zm_packet->packet->size and !zm_packet->decoded) {
|
||||
Debug(4, "Decoding");
|
||||
if (!zm_packet->decode(video_in_ctx)) {
|
||||
Debug(2, "unable to decode yet.");
|
||||
return 0;
|
||||
}
|
||||
// Go straight to out frame
|
||||
swscale.Convert(zm_packet->in_frame.get(), out_frame);
|
||||
} else {
|
||||
Error("Have neither in_frame or image in packet %d!",
|
||||
zm_packet->image_index);
|
||||
return 0;
|
||||
} // end if has packet or image
|
||||
Error("Have neither in_frame or image in packet %d!", zm_packet->image_index);
|
||||
return 0;
|
||||
} else {
|
||||
// Have in_frame.... may need to convert it to out_frame
|
||||
swscale.Convert(zm_packet->in_frame.get(), zm_packet->out_frame.get());
|
||||
@@ -1278,9 +1266,11 @@ int VideoStore::writeVideoFramePacket(const std::shared_ptr<ZMPacket> zm_packet)
|
||||
video_out_stream->time_base.den);
|
||||
|
||||
if (video_last_pts != AV_NOPTS_VALUE) {
|
||||
opkt->duration = opkt->pts - video_last_pts;
|
||||
opkt->duration = opkt->pts - video_last_pts;
|
||||
Debug(1, "Duration %" PRId64 " from pts %" PRId64 " - last %" PRId64, opkt->duration, opkt->pts, video_last_pts);
|
||||
if (opkt->duration < 0) opkt->duration = 0;
|
||||
}
|
||||
video_last_pts = zm_packet->in_frame->pts;
|
||||
video_last_pts = opkt->pts;
|
||||
write_packet(opkt.get(), video_out_stream);
|
||||
} // end while receive_packet
|
||||
} else { // Passthrough
|
||||
|
||||
@@ -76,7 +76,7 @@ if (isset($_REQUEST['sort'])) {
|
||||
|
||||
// Offset specifies the starting row to return, used for pagination
|
||||
$offset = 0;
|
||||
if (isset($_REQUEST['offset'])) {
|
||||
if (isset($_REQUEST['offset']) and ($_REQUEST['offset'] != 'NaN')) {
|
||||
if ((!is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']))) {
|
||||
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']);
|
||||
} else {
|
||||
@@ -87,7 +87,7 @@ if (isset($_REQUEST['offset'])) {
|
||||
// Limit specifies the number of rows to return
|
||||
// Set the default to 0 for events view, to prevent an issue with ALL pagination
|
||||
$limit = 0;
|
||||
if (isset($_REQUEST['limit'])) {
|
||||
if (isset($_REQUEST['limit']) and ($_REQUEST['limit'] != 'NaN')) {
|
||||
if ((!is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']))) {
|
||||
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
|
||||
} else {
|
||||
|
||||
@@ -82,9 +82,15 @@ class EventsController extends AppController {
|
||||
// TODO: Implement request based limits.
|
||||
|
||||
# 'limit' => '100',
|
||||
|
||||
|
||||
'order' => array('StartDateTime'),
|
||||
'paramType' => 'querystring',
|
||||
);
|
||||
if ($this->request->query('limit')) {
|
||||
$settings['limit'] = $this->request->query('limit');
|
||||
}
|
||||
|
||||
if ( isset($conditions['GroupId']) ) {
|
||||
$settings['joins'] = array(
|
||||
array(
|
||||
@@ -99,17 +105,8 @@ class EventsController extends AppController {
|
||||
}
|
||||
$settings['conditions'] = array($conditions, $mon_options);
|
||||
|
||||
// How many events to return
|
||||
$this->loadModel('Config');
|
||||
$limit = $this->Config->find('list', array(
|
||||
'conditions' => array('Name' => 'ZM_WEB_EVENTS_PER_PAGE'),
|
||||
'fields' => array('Name', 'Value')
|
||||
));
|
||||
$this->Paginator->settings = $settings;
|
||||
$events = $this->Paginator->paginate('Event');
|
||||
|
||||
// For each event, get the frameID which has the largest score
|
||||
// also add FS path
|
||||
$events = $this->Event->find('all', $settings);
|
||||
// For each event, get the frameID which has the largest score also add FS path
|
||||
|
||||
foreach ( $events as $key => $value ) {
|
||||
$EventObj = new ZM\Event($value['Event']);
|
||||
|
||||
@@ -27,7 +27,7 @@ define( 'ZM_CONFIG_SUBDIR', '@ZM_CONFIG_SUBDIR@' ); // Path to config subfolder
|
||||
define( 'ZM_VERSION', '@VERSION@' ); // Version
|
||||
define( 'ZM_DIR_TEMP', '@ZM_TMPDIR@' );
|
||||
define( 'ZM_DIR_CACHE', '@ZM_CACHEDIR@' );
|
||||
global $configvals;
|
||||
global $zm_configvals;
|
||||
|
||||
$configFile = ZM_CONFIG;
|
||||
$localConfigFile = basename($configFile);
|
||||
@@ -40,8 +40,8 @@ if ( file_exists($localConfigFile) && filesize($localConfigFile) > 0 ) {
|
||||
}
|
||||
|
||||
# Process name, value pairs from the main config file first
|
||||
$configvals = process_configfile($configFile);
|
||||
if (!$configvals) $configvals = [];
|
||||
$zm_configvals = process_configfile($configFile);
|
||||
if (!$zm_configvals) $zm_configvals = [];
|
||||
|
||||
# Search for user created config files. If one or more are found then
|
||||
# update our config value array with those values
|
||||
@@ -51,7 +51,7 @@ if ( is_dir($configSubFolder) ) {
|
||||
foreach ( glob($configSubFolder.'/*.conf') as $filename ) {
|
||||
//error_log("processing $filename");
|
||||
$newconfigvals = process_configfile($filename);
|
||||
if ($newconfigvals) $configvals = array_replace($configvals, $newconfigvals);
|
||||
if ($newconfigvals) $zm_configvals = array_replace($zm_configvals, $newconfigvals);
|
||||
}
|
||||
} else {
|
||||
error_log('WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on '.$configSubFolder);
|
||||
@@ -62,7 +62,7 @@ if ( is_dir($configSubFolder) ) {
|
||||
|
||||
# Now that our array our finalized, define each key => value
|
||||
# pair in the array as a constant
|
||||
foreach ( $configvals as $key => $value ) {
|
||||
foreach ( $zm_configvals as $key => $value ) {
|
||||
define($key, $value);
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ $GLOBALS['defaultUser'] = array(
|
||||
);
|
||||
|
||||
function loadConfig( $defineConsts=true ) {
|
||||
global $config;
|
||||
global $zm_config;
|
||||
global $dbConn;
|
||||
|
||||
$config = array();
|
||||
@@ -238,18 +238,18 @@ function loadConfig( $defineConsts=true ) {
|
||||
if ( !$result )
|
||||
echo mysql_error();
|
||||
while( $row = dbFetchNext($result) ) {
|
||||
$config[$row['Name']] = $row;
|
||||
$zm_config[$row['Name']] = $row;
|
||||
|
||||
if ( $defineConsts ) {
|
||||
# Values in conf.d files override db so check if already defined and update value
|
||||
if ( ! defined($row['Name']) ) {
|
||||
define($row['Name'], $row['Value']);
|
||||
} else {
|
||||
$config[$row['Name']]['Value'] = constant($row['Name']);
|
||||
$zm_config[$row['Name']]['Value'] = constant($row['Name']);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $config;
|
||||
return $zm_config;
|
||||
} # end function loadConfig
|
||||
|
||||
require_once('Server.php');
|
||||
|
||||
@@ -160,3 +160,7 @@ body.sticky #monitorList thead {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.colThumbnail img:hover {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@@ -296,6 +296,7 @@ function getNormalNavBarHTML($running, $user, $bandwidth_options, $view, $skin)
|
||||
echo getReportsHTML($view);
|
||||
echo getRprtEvntAuditHTML($view);
|
||||
echo getMapHTML($view);
|
||||
echo getAdditionalLinksHTML($view);
|
||||
echo getHeaderFlipHTML();
|
||||
echo '</ul></div><div id="accountstatus">
|
||||
';
|
||||
@@ -426,6 +427,7 @@ function getCollapsedNavBarHTML($running, $user, $bandwidth_options, $view, $ski
|
||||
echo getReportsHTML($view);
|
||||
echo getRprtEvntAuditHTML($view);
|
||||
echo getMapHTML($view);
|
||||
echo getAdditionalLinksHTML($view);
|
||||
echo '</ul>';
|
||||
}
|
||||
?>
|
||||
@@ -885,6 +887,23 @@ function getMapHTML($view) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Returns the html representing the content of the ZM_WEB_NAVBAR_LINKS content
|
||||
|
||||
function getAdditionalLinksHTML($view) {
|
||||
$result = '';
|
||||
|
||||
if (defined('ZM_WEB_NAVBAR_LINKS')) {
|
||||
if (ZM_WEB_NAVBAR_LINKS) {
|
||||
foreach (explode(',', ZM_WEB_NAVBAR_LINKS) as $link) {
|
||||
$result .= '<li class="nav-item">'.$link.'</li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
// Returns the html representing the header collapse toggle menu item
|
||||
function getHeaderFlipHTML() {
|
||||
$result = '';
|
||||
|
||||
@@ -1076,6 +1076,15 @@ function thumbnail_onmouseover(event) {
|
||||
const imgClass = ( currentView == 'console' ) ? 'zoom-console' : 'zoom';
|
||||
const imgAttr = ( currentView == 'frames' ) ? 'full_img_src' : 'stream_src';
|
||||
img.src = img.getAttribute(imgAttr);
|
||||
if ( currentView == 'console' ) {
|
||||
const rect = img.getBoundingClientRect();
|
||||
const zoomHeight = rect.height * 5; // scale factor defined in css
|
||||
if ( rect.bottom + (zoomHeight - rect.height) > window.innerHeight ) {
|
||||
img.style.transformOrigin = '0% 100%';
|
||||
} else {
|
||||
img.style.transformOrigin = '0% 0%';
|
||||
}
|
||||
}
|
||||
thumbnail_timeout = setTimeout(function() {
|
||||
img.classList.add(imgClass);
|
||||
}, 250);
|
||||
@@ -1088,6 +1097,9 @@ function thumbnail_onmouseout(event) {
|
||||
var imgAttr = ( currentView == 'frames' ) ? 'img_src' : 'still_src';
|
||||
img.src = img.getAttribute(imgAttr);
|
||||
img.classList.remove(imgClass);
|
||||
if ( currentView == 'console' ) {
|
||||
img.style.transformOrigin = '';
|
||||
}
|
||||
}
|
||||
|
||||
function initThumbAnimation() {
|
||||
|
||||
@@ -145,8 +145,8 @@ stateStrings[STATE_ALERT] = "<?php echo translate('Alert') ?>";
|
||||
global $user;
|
||||
if ($user) {
|
||||
// Only include config if logged in or auth turned off. The login view doesn't require any config.
|
||||
global $config;
|
||||
foreach ($config as $name=>$c) {
|
||||
global $zm_config;
|
||||
foreach ($zm_config as $name=>$c) {
|
||||
if (!$c['Private']) {
|
||||
$value = preg_replace('/(\n\r?)/', '\\\\$1', $c['Value']);
|
||||
$value = preg_replace('/\'/', '\\\\\'', $value);
|
||||
|
||||
@@ -88,7 +88,7 @@ foreach ($dnsmasq_config as $name=>$value) {
|
||||
# Handled below
|
||||
} else {
|
||||
echo '<div class="row"><label class="form-label">'.$name.'</label><span class="value">'.PHP_EOL;
|
||||
echo '<input type="text" name="config['.validHtmlStr($name).']" value="'.validHtmlStr($value).'"/></span></div>'.PHP_EOL;
|
||||
echo '<input type="text" name="dnsmasq_config['.validHtmlStr($name).']" value="'.validHtmlStr($value).'"/></span></div>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -97,24 +97,25 @@ foreach ($dnsmasq_config as $name=>$value) {
|
||||
<div class="leases"><h2>Leases</h2>
|
||||
<?php
|
||||
function process_dnsmasq_configfile($configFile) {
|
||||
$configvals = array();
|
||||
$our_configvals = array();
|
||||
if (is_readable($configFile)) {
|
||||
$cfg = fopen($configFile, 'r') or ZM\Error('Could not open config file: '.$configFile);
|
||||
while ( !feof($cfg) ) {
|
||||
$str = fgets($cfg, 256);
|
||||
if ( preg_match('/^\s*(#.*)?$/', $str) ) {
|
||||
continue;
|
||||
} else if ( preg_match('/^\s*([^=\s]+)\s*(=\s*[\'"]*(.*?)[\'"]*\s*)?$/', $str, $matches) ) {
|
||||
$configvals[$matches[1]] = isset($matches[3]) ? $matches[3] : 'yes';
|
||||
} else {
|
||||
ZM\Error("Malformed line in config $configFile\n$str");
|
||||
}
|
||||
} else if ( preg_match('/^\s*([^=\s]+)\s*(=\s*option:[^=\s]+)?(=\s*[\'"]*(.*?)[\'"]*\s*)?$/', $str, $matches) ) {
|
||||
//ZM\Debug(print_r($matches, true));
|
||||
$our_configvals[$matches[1].(isset($matches[2])?$matches[2]:'')] = isset($matches[4]) ? $matches[4] : 'yes';
|
||||
} else {
|
||||
ZM\Error("Malformed line in config $configFile\n$str");
|
||||
}
|
||||
}
|
||||
fclose($cfg);
|
||||
} else {
|
||||
ZM\Error('WARNING: dnsmasq configuration file found but is not readable. Check file permissions on '.$configFile);
|
||||
}
|
||||
return $configvals;
|
||||
return $our_configvals;
|
||||
}
|
||||
|
||||
function read_leasefile($file) {
|
||||
|
||||
@@ -41,7 +41,7 @@ echo getNavBarHTML();
|
||||
</div>
|
||||
|
||||
<!-- Table styling handled by bootstrap-tables -->
|
||||
<div class="row justify-content-center table-responsive-sm">
|
||||
<div id="content" class="row justify-content-center table-responsive-sm">
|
||||
<table
|
||||
id="framesTable"
|
||||
data-locale="<?php echo i18n() ?>"
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
var LOADING = 1;
|
||||
|
||||
var ajax = null;
|
||||
var wait_for_events_interval = null;
|
||||
|
||||
|
||||
function evaluateLoadTimes() {
|
||||
if (liveMode != 1 && currentSpeed == 0) return; // don't evaluate when we are not moving as we can do nothing really fast.
|
||||
|
||||
@@ -41,30 +50,37 @@ function evaluateLoadTimes() {
|
||||
// limit this from about 40fps to .1 fps
|
||||
currentDisplayInterval = Math.min(Math.max(currentDisplayInterval, 40), 10000);
|
||||
imageLoadTimesEvaluated=0;
|
||||
setSpeed(speedIndex);
|
||||
//setSpeed(speedIndex);
|
||||
$j('#fps').text("Display refresh rate is " + (1000 / currentDisplayInterval).toFixed(1) + " per second, avgFrac=" + avgFrac.toFixed(3) + ".");
|
||||
} // end evaluateLoadTimes()
|
||||
|
||||
function findEventByTime(arr, time, debug) {
|
||||
function findEventByTime(arr, time, debug=false) {
|
||||
let start = 0;
|
||||
let end = arr.length-1; // -1 because 0 based indexing
|
||||
|
||||
//console.log("looking for "+time+" Start: " + arr[start].StartTimeSecs + ' End: ' + arr[end].EndTimeSecs);
|
||||
if (debug) {
|
||||
if ( arr.length ) {
|
||||
console.log("looking for "+time+" Start: " + arr[start].StartTimeSecs + ' End: ' + arr[end].EndTimeSecs);
|
||||
} else {
|
||||
console.log("looking for "+time+" but nothing in arr");
|
||||
}
|
||||
}
|
||||
// Iterate while start not meets end
|
||||
while ((start <= end) && (arr[start].StartTimeSecs <= time) && (!arr[end].EndTimeSecs || (arr[end].EndTimeSecs >= time))) {
|
||||
//console.log("looking for "+time+" Start: " + arr[start].StartTimeSecs + ' End: ' + arr[end].EndTimeSecs);
|
||||
if (debug)
|
||||
console.log("looking for "+time+" Start: " + arr[start].StartTimeSecs + ' End: ' + arr[end].EndTimeSecs);
|
||||
// Find the middle index
|
||||
const middle = Math.floor((start + end)/2);
|
||||
const zm_event = arr[middle];
|
||||
|
||||
// If element is present at mid, return True
|
||||
//console.log(middle, zm_event, time);
|
||||
if (debug) console.log(middle, zm_event, time);
|
||||
if ((zm_event.StartTimeSecs <= time) && (!zm_event.EndTimeSecs || (zm_event.EndTimeSecs >= time))) {
|
||||
//console.log("Found it at ", zm_event);
|
||||
if (debug) console.log("Found it at ", zm_event);
|
||||
return zm_event;
|
||||
}
|
||||
|
||||
//console.log("Didn't find it looking for "+time+" Start: " + zm_event.StartTimeSecs + ' End: ' + zm_event.EndTimeSecs);
|
||||
if (debug) console.log("Didn't find it looking for "+time+" Start: " + zm_event.StartTimeSecs + ' End: ' + zm_event.EndTimeSecs);
|
||||
// Else look in left or right half accordingly
|
||||
if (zm_event.StartTimeSecs < time) {
|
||||
start = middle + 1;
|
||||
@@ -77,7 +93,7 @@ function findEventByTime(arr, time, debug) {
|
||||
return false;
|
||||
} // end function findEventByTime
|
||||
|
||||
function findFrameByTime(arr, time, debug) {
|
||||
function findFrameByTime(arr, time, debug=false) {
|
||||
if (!arr) {
|
||||
console.log("No array in findFrameByTime");
|
||||
return false;
|
||||
@@ -149,7 +165,7 @@ function findFrameByTime(arr, time, debug) {
|
||||
break;
|
||||
}
|
||||
} // end while
|
||||
if (debug) console.log("Didn't find it");
|
||||
if (debug) console.log("Didn't find frame it");
|
||||
return false;
|
||||
} // end function findFrameByTime(arr, time, debug=false)
|
||||
|
||||
@@ -180,7 +196,6 @@ function getFrame(monId, time, last_Frame) {
|
||||
|
||||
let Event = findEventByTime(events_for_monitor[monId], time, false);
|
||||
if (Event === false) {
|
||||
// This might be better with a binary search
|
||||
for (let i=0, len=events_for_monitor[monId].length; i<len; i++) {
|
||||
const event_id = events_for_monitor[monId][i].Id;
|
||||
const e = events[event_id];
|
||||
@@ -207,19 +222,28 @@ function getFrame(monId, time, last_Frame) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Event) return;
|
||||
if (!Event) {
|
||||
console.log('No event found for ' + time + ' ' + secs2inputstr(time) + ' on monitor ' + monId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Event.FramesById) {
|
||||
// It is assumed at this time that every event has frames
|
||||
console.log('No FramesById for event ', Event.Id);
|
||||
load_Frames({event_id: Event}).then(function() {
|
||||
var event_list = {};
|
||||
event_list[Event.Id] = Event;
|
||||
loadFrames(event_list).then(function() {
|
||||
if (!Event.FramesById) {
|
||||
console.log("No FramesById after load_Frames!", Event);
|
||||
console.log("No FramesById after loadFrames!", Event);
|
||||
}
|
||||
return findFrameByTime(Event.FramesById, time);
|
||||
}, function(Error) {
|
||||
console.log(Error);
|
||||
});
|
||||
return;
|
||||
} else if (!Event.FramesById.length) {
|
||||
console.log("frames loading for event " + Event.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to get frame by time, not some fun calc that assumes frames have the same length.
|
||||
@@ -302,37 +326,32 @@ function getImageSource(monId, time) {
|
||||
return;
|
||||
}
|
||||
|
||||
let scale = parseInt(100*monitorCanvasObj[monId].width / monitorWidth[monId]);
|
||||
let scale = parseInt(100 * monitorCanvasObj[monId].width / monitorWidth[monId]);
|
||||
if (scale > 100) {
|
||||
scale = 100;
|
||||
} else {
|
||||
scale = 10 * parseInt(scale/10);
|
||||
scale = 10 * parseInt(scale/10); // Round to nearest 10
|
||||
// May need to limit how small we can go to maintain fidelity
|
||||
}
|
||||
|
||||
|
||||
// Storage[0] is guaranteed to exist as we make sure it is there in montagereview.js.php
|
||||
const storage = Storage[e.StorageId] ? Storage[e.StorageId] : Storage[0];
|
||||
// monitorServerId may be 0, which gives us the default Server entry
|
||||
const server = storage.ServerId ? Servers[storage.ServerId] : Servers[monitorServerId[monId]];
|
||||
return server.PathToZMS + '?mode=jpeg&frames=1&event=' + Frame.EventId + '&frame='+frame_id +
|
||||
return server.PathToZMS + '?mode=jpeg&event=' + Frame.EventId + '&frame='+frame_id +
|
||||
//"&width=" + monitorCanvasObj[monId].width +
|
||||
//"&height=" + monitorCanvasObj[monId].height +
|
||||
"&scale=" + scale +
|
||||
"&frames=1" +
|
||||
"&rate=" + 100*speeds[speedIndex] +
|
||||
'&' + auth_relay;
|
||||
|
||||
return server.PathToIndex +
|
||||
'?view=image&eid=' + Frame.EventId + '&fid='+frame_id +
|
||||
"&width=" + monitorCanvasObj[monId].width +
|
||||
"&height=" + monitorCanvasObj[monId].height;
|
||||
} // end found Frame
|
||||
return '';
|
||||
} // end function getImageSource
|
||||
|
||||
// callback when loading an image. Will load itself to the canvas, or draw no data
|
||||
function imagedone( obj, monId, success ) {
|
||||
if ( success ) {
|
||||
if (success) {
|
||||
const canvasCtx = monitorCanvasCtx[monId];
|
||||
const canvasObj = monitorCanvasObj[monId];
|
||||
|
||||
@@ -369,42 +388,25 @@ function imagedone( obj, monId, success ) {
|
||||
return;
|
||||
}
|
||||
|
||||
function loadNoData( monId ) {
|
||||
if ( monId ) {
|
||||
var canvasCtx = monitorCanvasCtx[monId];
|
||||
var canvasObj = monitorCanvasObj[monId];
|
||||
canvasCtx.fillStyle="white";
|
||||
canvasCtx.fillRect(0, 0, canvasObj.width, canvasObj.height);
|
||||
var textSize=canvasObj.width * 0.15;
|
||||
var text="No Event";
|
||||
canvasCtx.font = "600 " + textSize.toString() + "px Arial";
|
||||
canvasCtx.fillStyle="black";
|
||||
var textWidth = canvasCtx.measureText(text).width;
|
||||
canvasCtx.fillText(text, canvasObj.width/2 - textWidth/2, canvasObj.height/2);
|
||||
} else {
|
||||
console.log("No monId in loadNoData");
|
||||
}
|
||||
}
|
||||
|
||||
function writeText( monId, text ) {
|
||||
if ( monId ) {
|
||||
var canvasCtx = monitorCanvasCtx[monId];
|
||||
var canvasObj = monitorCanvasObj[monId];
|
||||
function writeText(monId, text) {
|
||||
if (monId) {
|
||||
const canvasCtx = monitorCanvasCtx[monId];
|
||||
const canvasObj = monitorCanvasObj[monId];
|
||||
//canvasCtx.fillStyle="white";
|
||||
//canvasCtx.fillRect(0, 0, canvasObj.width, canvasObj.height);
|
||||
var textSize=canvasObj.width * 0.15;
|
||||
canvasCtx.font = "600 " + textSize.toString() + "px Arial";
|
||||
canvasCtx.fillStyle="white";
|
||||
var textSize = canvasObj.width * 0.15;
|
||||
canvasCtx.font = '600 ' + textSize.toString() + "px Arial";
|
||||
canvasCtx.fillStyle = 'white';
|
||||
var textWidth = canvasCtx.measureText(text).width;
|
||||
canvasCtx.fillText(text, canvasObj.width/2 - textWidth/2, canvasObj.height/2);
|
||||
} else {
|
||||
console.log("No monId in loadNoData");
|
||||
console.log('No monId in writeText');
|
||||
}
|
||||
}
|
||||
|
||||
// Either draws the
|
||||
function loadImage2Monitor( monId, url ) {
|
||||
if ( monitorLoading[monId] && monitorImageObject[monId].src != url ) {
|
||||
function loadImage2Monitor(monId, url) {
|
||||
if ( monitorLoading[monId] && (monitorImageObject[monId].src != url) ) {
|
||||
// never queue the same image twice (if it's loading it has to be defined, right?
|
||||
monitorLoadingStageURL[monId] = url; // we don't care if we are overriting, it means it didn't change fast enough
|
||||
} else {
|
||||
@@ -423,103 +425,91 @@ function loadImage2Monitor( monId, url ) {
|
||||
function timerFire() {
|
||||
// See if we need to reschedule
|
||||
if ( ( currentDisplayInterval != timerInterval ) || ( currentSpeed == 0 ) ) {
|
||||
console.log("Turn off interrupts timerInterfave", timerInterval, currentDisplayInterval, currentSpeed);
|
||||
// zero just turn off interrupts
|
||||
clearInterval(timerObj);
|
||||
timerObj = null;
|
||||
timerInterval = currentDisplayInterval;
|
||||
console.log("Turn off interrupts timerInterfave" + timerInterval);
|
||||
}
|
||||
|
||||
if ( (currentSpeed > 0 || liveMode != 0) && ! timerObj ) {
|
||||
timerObj = setInterval(timerFire, timerInterval); // don't fire out of live mode if speed is zero
|
||||
}
|
||||
|
||||
if (liveMode) {
|
||||
outputUpdate(currentTimeSecs); // In live mode we basically do nothing but redisplay
|
||||
} else if (currentTimeSecs + playSecsPerInterval >= maxTimeSecs) {
|
||||
// beyond the end just stop
|
||||
console.log("Current time " + currentTimeSecs + " + " + playSecsPerInterval + " >= " + maxTimeSecs + " so stopping");
|
||||
if (speedIndex) setSpeed(0);
|
||||
outputUpdate(currentTimeSecs);
|
||||
} else {
|
||||
//console.log("Current time " + currentTimeSecs + " + " + playSecsPerInterval);
|
||||
} else if (playSecsPerInterval || (currentTimeSecs==minTimeSecs)) {
|
||||
outputUpdate(playSecsPerInterval + currentTimeSecs);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((currentSpeed > 0 || liveMode != 0) && !timerObj) {
|
||||
timerObj = setInterval(timerFire, timerInterval); // don't fire out of live mode if speed is zero
|
||||
} else {
|
||||
console.log("timefire", "CurrentSpeed", currentSpeed, "liveMode", liveMode, timerObj);
|
||||
}
|
||||
} // end function timerFire()
|
||||
|
||||
// val is seconds?
|
||||
function drawSliderOnGraph(val) {
|
||||
var sliderWidth=10;
|
||||
var sliderLineWidth=1;
|
||||
var sliderHeight=cHeight;
|
||||
if (numMonitors <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( liveMode == 1 ) {
|
||||
val = Math.floor( Date.now() / 1000);
|
||||
}
|
||||
// Set some sizes
|
||||
|
||||
var sliderWidth=10;
|
||||
var sliderLineWidth=1;
|
||||
var sliderHeight=cHeight;
|
||||
// Set some sizes
|
||||
var labelpx = Math.max( 6, Math.min( 20, parseInt(cHeight * timeLabelsFractOfRow / (numMonitors+1)) ) );
|
||||
var labbottom = parseInt(cHeight * 0.2 / (numMonitors+1)).toString() + "px"; // This is positioning same as row labels below, but from bottom so 1-position
|
||||
var labfont = labelpx + "px"; // set this like below row labels
|
||||
|
||||
if ( numMonitors > 0 ) {
|
||||
// if we have no data to display don't do the slider itself
|
||||
var sliderX = parseInt((val - minTimeSecs) / rangeTimeSecs * cWidth - sliderWidth/2); // position left side of slider
|
||||
if ( sliderX < 0 ) sliderX = 0;
|
||||
if ( sliderX + sliderWidth > cWidth ) {
|
||||
sliderX = cWidth-sliderWidth-1;
|
||||
}
|
||||
// if we have no data to display don't do the slider itself
|
||||
let sliderX = parseInt((val - minTimeSecs) / rangeTimeSecs * cWidth - sliderWidth/2); // position left side of slider
|
||||
if ( sliderX < 0 ) sliderX = 0;
|
||||
if ( sliderX + sliderWidth > cWidth ) sliderX = cWidth-sliderWidth-1;
|
||||
|
||||
// If we have data already saved first restore it from LAST time
|
||||
// If we have data already saved first restore it from LAST time
|
||||
|
||||
if ( typeof underSlider !== 'undefined' ) {
|
||||
ctx.putImageData(underSlider, underSliderX, 0, 0, 0, sliderWidth, sliderHeight);
|
||||
underSlider = undefined;
|
||||
}
|
||||
if ( liveMode == 0 ) {
|
||||
// we get rid of the slider if we switch to live (since it may not be in the "right" place)
|
||||
// Now save where we are putting it THIS time
|
||||
underSlider = ctx.getImageData(sliderX, 0, sliderWidth, sliderHeight);
|
||||
// And add in the slider'
|
||||
ctx.lineWidth = sliderLineWidth;
|
||||
ctx.strokeStyle = 'yellow';
|
||||
// looks like strokes are on the outside (or could be) so shrink it by the line width so we replace all the pixels
|
||||
ctx.strokeRect(sliderX+sliderLineWidth, sliderLineWidth, sliderWidth - 2*sliderLineWidth, sliderHeight - 2*sliderLineWidth);
|
||||
underSliderX = sliderX;
|
||||
}
|
||||
var o = document.getElementById('scruboutput');
|
||||
if ( liveMode == 1 ) {
|
||||
o.innerHTML = "Live Feed @ " + (1000 / currentDisplayInterval).toFixed(1) + " fps";
|
||||
o.style.color = "red";
|
||||
} else {
|
||||
o.innerHTML = secs2dbstr(val);
|
||||
o.style.color = 'white';
|
||||
}
|
||||
o.style.position = "absolute";
|
||||
o.style.bottom = labbottom;
|
||||
o.style.font = labfont;
|
||||
// try to get length and then when we get too close to the right switch to the left
|
||||
var len = o.offsetWidth;
|
||||
var x;
|
||||
if ( sliderX > cWidth/2 ) {
|
||||
x = sliderX - len - 10;
|
||||
} else {
|
||||
x = sliderX + 10;
|
||||
}
|
||||
o.style.left = x.toString() + "px";
|
||||
if ( typeof underSlider !== 'undefined' ) {
|
||||
ctx.putImageData(underSlider, underSliderX, 0, 0, 0, sliderWidth, sliderHeight);
|
||||
underSlider = undefined;
|
||||
}
|
||||
if ( liveMode == 0 ) {
|
||||
// we get rid of the slider if we switch to live (since it may not be in the "right" place)
|
||||
// Now save where we are putting it THIS time
|
||||
underSlider = ctx.getImageData(sliderX, 0, sliderWidth, sliderHeight);
|
||||
// And add in the slider'
|
||||
ctx.lineWidth = sliderLineWidth;
|
||||
ctx.strokeStyle = 'yellow';
|
||||
// looks like strokes are on the outside (or could be) so shrink it by the line width so we replace all the pixels
|
||||
ctx.strokeRect(sliderX+sliderLineWidth, sliderLineWidth, sliderWidth - 2*sliderLineWidth, sliderHeight - 2*sliderLineWidth);
|
||||
underSliderX = sliderX;
|
||||
}
|
||||
var o = document.getElementById('scruboutput');
|
||||
if ( liveMode == 1 ) {
|
||||
o.innerHTML = 'Live Feed @ ' + (1000 / currentDisplayInterval).toFixed(1) + ' fps';
|
||||
o.style.color = 'red';
|
||||
} else {
|
||||
o.innerHTML = secs2dbstr(val);
|
||||
o.style.color = 'white';
|
||||
}
|
||||
o.style.position = 'absolute';
|
||||
o.style.bottom = labbottom;
|
||||
o.style.font = labfont;
|
||||
// try to get length and then when we get too close to the right switch to the left
|
||||
var len = o.offsetWidth;
|
||||
const x = (sliderX > cWidth/2) ? sliderX - len - 10 : sliderX + 10;
|
||||
o.style.left = x.toString() + "px";
|
||||
|
||||
// This displays (or not) the left/right limits depending on how close the slider is.
|
||||
// Because these change widths if the slider is too close, use the slider width as an estimate for the left/right label length (i.e. don't recalculate len from above)
|
||||
// If this starts to collide increase some of the extra space
|
||||
|
||||
var o = document.getElementById('scrubleft');
|
||||
o.innerHTML = secs2dbstr(minTimeSecs);
|
||||
o.style.position = "absolute";
|
||||
o.style.bottom = labbottom;
|
||||
o.style.font = labfont;
|
||||
o.style.left = "5px";
|
||||
o = document.getElementById('scrubleft');
|
||||
if ( numMonitors == 0 ) { // we need a len calculation if we skipped the slider
|
||||
len = o.offsetWidth;
|
||||
}
|
||||
@@ -532,13 +522,7 @@ function drawSliderOnGraph(val) {
|
||||
o.style.display = "inline-flex"; // safari won't take this but will just ignore
|
||||
}
|
||||
|
||||
var o = document.getElementById('scrubright');
|
||||
o.innerHTML = secs2dbstr(maxTimeSecs);
|
||||
o.style.position = "absolute";
|
||||
o.style.bottom = labbottom;
|
||||
o.style.font = labfont;
|
||||
// If the slider will overlay part of this suppress (this is the right side)
|
||||
o.style.left=(cWidth - len - 15).toString() + "px";
|
||||
o = document.getElementById('scrubright');
|
||||
if ( sliderX > cWidth - len - 20 || cWidth < len * 4 ) {
|
||||
o.style.display = "none";
|
||||
} else {
|
||||
@@ -547,68 +531,89 @@ function drawSliderOnGraph(val) {
|
||||
}
|
||||
}
|
||||
|
||||
function drawFrameOnGraph(frame) {
|
||||
if (!frame.Score) return;
|
||||
// Now put in scored frames (if any)
|
||||
let x1 = parseInt( (frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth); // round low end down
|
||||
let x2 = parseInt( (frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ); // round up
|
||||
if (x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
|
||||
ctx.fillStyle=monitorColour[Event.MonitorId];
|
||||
//ctx.fillStyle = '#ff0000';
|
||||
ctx.globalAlpha = 0.4 + 0.6 * (1 - frame.Score/maxScore); // Background is scaled but even lowest is twice as dark as the background
|
||||
const MonitorId = events[frame.EventId].MonitorId;
|
||||
ctx.fillRect(x1, monitorIndex[MonitorId]*rowHeight, x2-x1, rowHeight-2);
|
||||
//console.log("Drew frame from ", x1, MonitorId, monitorIndex[MonitorId]*rowHeight, x2-x1, rowHeight);
|
||||
}
|
||||
|
||||
function drawEventOnGraph(Event) {
|
||||
// round low end down
|
||||
const x1 = parseInt((Event.StartTimeSecs - minTimeSecs) / rangeTimeSecs * cWidth);
|
||||
if (!Event.EndTimeSecs) Event.EndTimeSecs = maxTimeSecs;
|
||||
// round high end up to be sure consecutive ones connect
|
||||
const x2 = parseInt((Event.EndTimeSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 );
|
||||
if (!monitorColour[Event.MonitorId]) {
|
||||
console.log("No colour for ", Event.MonitorId, monitorColour);
|
||||
ctx.fillStyle = '#43bcf2';
|
||||
} else {
|
||||
ctx.fillStyle = monitorColour[Event.MonitorId];
|
||||
}
|
||||
ctx.globalAlpha = 0.2; // light color for background
|
||||
// Erase any overlap so it doesn't look artificially darker
|
||||
ctx.clearRect(x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight);
|
||||
ctx.fillRect(x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight-2);
|
||||
//outputUpdate(currentTimeSecs);
|
||||
//console.log("Drew event from ", x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight);
|
||||
}
|
||||
|
||||
function drawGraph() {
|
||||
var divWidth = document.getElementById('timelinediv').clientWidth;
|
||||
underSlider = undefined; // flag we don't have a slider cached
|
||||
const divWidth = document.getElementById('timelinediv').clientWidth;
|
||||
canvas.width = cWidth = divWidth; // Let it float and determine width (it should be sized a bit smaller percentage of window)
|
||||
cHeight = parseInt(window.innerHeight * 0.10);
|
||||
if ( cHeight < numMonitors * 20 ) {
|
||||
cHeight = parseInt(window.innerHeight * 0.10); // 10%
|
||||
if (cHeight < numMonitors * 20) { //Minimum 20px per monitor maybe it should be 10px per monitor?
|
||||
cHeight = numMonitors * 20;
|
||||
}
|
||||
|
||||
canvas.height = cHeight;
|
||||
|
||||
if ( events && ( Object.keys(events).length == 0 ) ) {
|
||||
ctx.globalAlpha = 1;
|
||||
if (events && ( Object.keys(events).length == 0 ) ) {
|
||||
ctx.font = "40px Georgia";
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillStyle = "white";
|
||||
var t = "No data found in range - choose differently";
|
||||
const t = LOADING ? "Loading events" : "No events found.";
|
||||
var l = ctx.measureText(t).width;
|
||||
ctx.fillText(t, (cWidth - l)/2, cHeight-10);
|
||||
underSlider = undefined;
|
||||
return;
|
||||
}
|
||||
var rowHeight = parseInt(cHeight / (numMonitors + 1) ); // Leave room for a scale of some sort
|
||||
|
||||
rowHeight = parseInt(cHeight / (numMonitors + 1) ); // Leave room for a scale of some sort
|
||||
// Note that this may be a sparse array
|
||||
|
||||
// Should we clear the canvas?
|
||||
|
||||
// first fill in the bars for the events (not alarms)
|
||||
|
||||
for ( var event_id in events ) {
|
||||
var Event = events[event_id];
|
||||
|
||||
// round low end down
|
||||
var x1 = parseInt((Event.StartTimeSecs - minTimeSecs) / rangeTimeSecs * cWidth);
|
||||
var x2 = parseInt((Event.EndTimeSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ); // round high end up to be sure consecutive ones connect
|
||||
ctx.fillStyle = monitorColour[Event.MonitorId];
|
||||
ctx.globalAlpha = 0.2; // light color for background
|
||||
ctx.clearRect(x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight); // Erase any overlap so it doesn't look artificially darker
|
||||
ctx.fillRect(x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight);
|
||||
|
||||
for ( var frame_id in Event.FramesById ) {
|
||||
var Frame = Event.FramesById[frame_id];
|
||||
if ( ! Frame.Score ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now put in scored frames (if any)
|
||||
var x1=parseInt( (Frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth); // round low end down
|
||||
var x2=parseInt( (Frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ); // round up
|
||||
if (x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
|
||||
//ctx.fillStyle=monitorColour[Event.MonitorId];
|
||||
ctx.globalAlpha = 0.4 + 0.6 * (1 - Frame.Score/maxScore); // Background is scaled but even lowest is twice as dark as the background
|
||||
ctx.fillRect(x1, monitorIndex[Event.MonitorId]*rowHeight, x2-x1, rowHeight);
|
||||
} // end foreach frame
|
||||
// At first, no events loaded, that's ok, later, we will have some events, should only draw those in the time range.
|
||||
for (const event_id in events) {
|
||||
const Event = events[event_id];
|
||||
drawEventOnGraph(Event);
|
||||
if (Event.FramesById) {
|
||||
for (const frame_id in Event.FramesById ) {
|
||||
const Frame = Event.FramesById[frame_id];
|
||||
if (!Frame.Score) continue;
|
||||
drawFrameOnGraph(Frame);
|
||||
} // end foreach frame
|
||||
}
|
||||
} // end foreach Event
|
||||
|
||||
for ( var i=0; i < numMonitors; i++ ) {
|
||||
// Note that this may be a sparse array
|
||||
for (let i=0; i < numMonitors; i++) {
|
||||
// Apparently we have to set these each time before calling fillText
|
||||
ctx.font = parseInt(rowHeight * timeLabelsFractOfRow).toString() + "px Georgia";
|
||||
ctx.fillStyle = "white";
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillStyle = "white";
|
||||
// This should roughly center font in row
|
||||
ctx.fillText(monitorName[monitorPtr[i]], 0, (i + 1 - (1 - timeLabelsFractOfRow)/2 ) * rowHeight);
|
||||
}
|
||||
underSlider = undefined; // flag we don't have a slider cached
|
||||
|
||||
drawSliderOnGraph(currentTimeSecs);
|
||||
return;
|
||||
} // end function drawGraph
|
||||
|
||||
function redrawScreen() {
|
||||
@@ -624,7 +629,7 @@ function redrawScreen() {
|
||||
var scaleDiv = $j('#ScaleDiv');
|
||||
var fit = $j('#fit');
|
||||
|
||||
if ( liveMode == 1 ) {
|
||||
if (liveMode == 1) {
|
||||
// if we are not in live view switch to history -- this has to come before fit in case we re-establish the timeline
|
||||
dateTimeDiv.hide();
|
||||
speedDiv.hide();
|
||||
@@ -646,12 +651,11 @@ function redrawScreen() {
|
||||
panLeft.show();
|
||||
panRight.show();
|
||||
downloadVideo.show();
|
||||
|
||||
drawGraph();
|
||||
}
|
||||
|
||||
var monitors = $j('#monitors');
|
||||
if ( fitMode == 1 ) {
|
||||
if (fitMode == 1) {
|
||||
var fps = $j('#fps');
|
||||
var vh = window.innerHeight;
|
||||
var mh = (vh - monitors.position().top - fps.outerHeight());
|
||||
@@ -680,13 +684,14 @@ function redrawScreen() {
|
||||
} // end function redrawScreen
|
||||
|
||||
function outputUpdate(time) {
|
||||
drawSliderOnGraph(time);
|
||||
for ( var i=0; i < numMonitors; i++ ) {
|
||||
var src = getImageSource(monitorPtr[i], time);
|
||||
//console.log("New image src: " + src);
|
||||
loadImage2Monitor(monitorPtr[i], src);
|
||||
if (Object.keys(events).length !== 0) {
|
||||
for ( let i=0; i < numMonitors; i++ ) {
|
||||
const src = getImageSource(monitorPtr[i], time);
|
||||
loadImage2Monitor(monitorPtr[i], src);
|
||||
}
|
||||
}
|
||||
currentTimeSecs = time;
|
||||
drawSliderOnGraph(time);
|
||||
}
|
||||
|
||||
// Found this here: http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element
|
||||
@@ -728,8 +733,9 @@ function tmove(event) {
|
||||
function mmove(event) {
|
||||
if ( mouseisdown ) {
|
||||
// only do anything if the mouse is depressed while on the sheet
|
||||
var sec = Math.floor(minTimeSecs + rangeTimeSecs / event.target.width * event.target.relMouseCoords(event).x);
|
||||
outputUpdate(sec);
|
||||
const relx = event.target.relMouseCoords(event).x;
|
||||
const sec = Math.floor(minTimeSecs + rangeTimeSecs / event.target.width * relx);
|
||||
if (parseInt(sec)) outputUpdate(sec);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -748,7 +754,7 @@ function secs2inputstr(s) {
|
||||
}
|
||||
|
||||
function secs2dbstr(s) {
|
||||
if ( ! parseInt(s) ) {
|
||||
if (!parseInt(s)) {
|
||||
console.log("Invalid value for " + s + " seconds");
|
||||
return '';
|
||||
}
|
||||
@@ -761,7 +767,7 @@ function secs2dbstr(s) {
|
||||
}
|
||||
|
||||
function setFit(value) {
|
||||
fitMode=value;
|
||||
fitMode = value;
|
||||
redrawScreen();
|
||||
}
|
||||
|
||||
@@ -896,6 +902,7 @@ function click_zoomout() {
|
||||
function click_panleft() {
|
||||
minTimeSecs = parseInt(minTimeSecs - rangeTimeSecs/2);
|
||||
maxTimeSecs = minTimeSecs + rangeTimeSecs - 1;
|
||||
currentTimeSecs -= rangeTimeSecs/2;
|
||||
clicknav(minTimeSecs, maxTimeSecs, 0);
|
||||
}
|
||||
function click_panright() {
|
||||
@@ -941,7 +948,7 @@ function allnon() {
|
||||
clicknav(0, 0, 0);
|
||||
}
|
||||
|
||||
// >>>>>>>>>>>>>>>> Handles individual monitor clicks and navigation to the standard event/watch display
|
||||
// Handles individual monitor clicks and navigation to the standard event/watch display
|
||||
|
||||
function showOneMonitor(monId, event) {
|
||||
// link out to the normal view of one event's data
|
||||
@@ -991,47 +998,201 @@ function clickMonitor(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
function changeDateTime(e) {
|
||||
/*
|
||||
var minTime_element = $j('#minTime');
|
||||
var maxTime_element = $j('#maxTime');
|
||||
function changeFilters(e) {
|
||||
console.log(e, this);
|
||||
// Need to update minTimeSecs and maxTimeSecs
|
||||
|
||||
var minTime = moment(minTime_element.val());
|
||||
var maxTime = moment(maxTime_element.val());
|
||||
if ( minTime.isAfter(maxTime) ) {
|
||||
maxTime_element.parent().addClass('has-error');
|
||||
return; // Don't reload because we have invalid datetime filter.
|
||||
let minMoment, maxMoment;
|
||||
|
||||
const matches = this.name.match(/^filter\[Query\]\[terms\]\[(\d+)\]\[val\]$/);
|
||||
console.log(matches);
|
||||
if (matches && matches.length) {
|
||||
const name = 'filter[Query][terms]['+matches[1]+'][attr]';
|
||||
|
||||
const attr = this.form.elements[name];
|
||||
if (attr && (attr.value == 'StartDateTime')) {
|
||||
const val = this;
|
||||
const op = this.form.elements['filter[Query][terms]['+matches[1]+'][op]'];
|
||||
if (op.value == '>=') {
|
||||
minMoment = moment(val.value, 'YYYY-MM-DD HH:mm:ss');
|
||||
if (!minMoment.isValid()) {
|
||||
alert("Date start is not valid." + val.value);
|
||||
return;
|
||||
}
|
||||
} else if (op == '<=') {
|
||||
maxMoment = moment(val.value, 'YYYY-MM-DD HH:mm:ss');
|
||||
if (!maxMoment.isValid()) maxMoment = moment();
|
||||
}
|
||||
} else {
|
||||
console.log("No attr", attr);
|
||||
}
|
||||
} else {
|
||||
maxTime_element.parent().removeClass('has-error');
|
||||
const regexp = /^filter\[Query\]\[terms\]\[(\d+)\]\[attr\]$/;
|
||||
$j('#fieldsTable input[value="StartDateTime"]').each(function(index) {
|
||||
const matches = this.name.match(regexp);
|
||||
console.log('looking at', this, matches);
|
||||
if (matches && matches.length) {
|
||||
const val = this.form.elements['filter[Query][terms]['+matches[1]+'][val]'];
|
||||
if (val) {
|
||||
const op = this.form.elements['filter[Query][terms]['+matches[1]+'][op]'];
|
||||
if (op.value == '>=') {
|
||||
minMoment = moment(val.value, 'YYYY-MM-DD HH:mm:ss');
|
||||
if (!minMoment.isValid()) {
|
||||
alert("Date start is not valid." + val.value);
|
||||
return;
|
||||
}
|
||||
} else if (op == '<=') {
|
||||
maxMoment = moment(val.value, 'YYYY-MM-DD HH:mm:ss');
|
||||
if (!maxMoment.isValid()) maxMoment = moment();
|
||||
}
|
||||
} else {
|
||||
console.log("no val ", matches);
|
||||
}
|
||||
} else {
|
||||
console.log("No matches for ", this.name);
|
||||
}
|
||||
});
|
||||
} // end if a datetime or something else
|
||||
if (minMoment) {
|
||||
minTimeSecs = minMoment.unix();
|
||||
console.log("Set minMoment to ", minTimeSecs);
|
||||
if (currentTimeSecs < minTimeSecs) {
|
||||
console.log("Adjusting currentTimeSecs", currentTimeSecs, minTimeSecs);
|
||||
currentTimeSecs = minTimeSecs;
|
||||
}
|
||||
} else {
|
||||
console.log("No minMoment");
|
||||
}
|
||||
|
||||
var minStr = "&minTime="+($j('#minTime')[0].value);
|
||||
var maxStr = "&maxTime="+($j('#maxTime')[0].value);
|
||||
*/
|
||||
|
||||
var zoomStr="";
|
||||
for ( var i=0; i < numMonitors; i++ ) {
|
||||
if ( monitorZoomScale[monitorPtr[i]] < 0.99 || monitorZoomScale[monitorPtr[i]] > 1.01 ) { // allow for some up/down changes and just treat as 1 of almost 1
|
||||
zoomStr += "&z" + monitorPtr[i].toString() + "=" + monitorZoomScale[monitorPtr[i]].toFixed(2);
|
||||
}
|
||||
if (maxMoment) {
|
||||
maxTimeSecs = maxMoment.unix();
|
||||
console.log(currentTimeSecs, minTimeSecs, maxTimeSecs);
|
||||
if (currentTimeSecs > maxTimeSecs) currentTimeSecs = maxTimeSecs;
|
||||
} else {
|
||||
console.log("No maxMoment");
|
||||
}
|
||||
|
||||
// Reloading can take a while, so stop interrupts to reduce load
|
||||
clearInterval(timerObj);
|
||||
timerObj = null;
|
||||
const form = $j('#montagereview_form');
|
||||
console.log(form.serialize());
|
||||
|
||||
var uri = "?" + form.serialize() + zoomStr + "&scale=" + $j("#scaleslider")[0].value + "&speed=" + speeds[$j("#speedslider")[0].value];
|
||||
//var uri = "?view=" + currentView + fitStr + minStr + maxStr + liveStr + zoomStr + "&scale=" + $j("#scaleslider")[0].value + "&speed=" + speeds[$j("#speedslider")[0].value];
|
||||
window.location = uri;
|
||||
drawGraph(); // Will use new values
|
||||
loadEventData();
|
||||
wait_for_events();
|
||||
}
|
||||
|
||||
// >>>>>>>>> Initialization that runs on window load by being at the bottom
|
||||
function loadEventData(e) {
|
||||
LOADING = true;
|
||||
|
||||
var monitors = monitorData;
|
||||
var data = {};
|
||||
var mon_ids = [];
|
||||
for (let monitor_i=0, monitors_len=monitors.length; monitor_i < monitors_len; monitor_i++) {
|
||||
const monitor = monitors[monitor_i];
|
||||
monitorLoading[monitor.Id] = false;
|
||||
mon_ids[mon_ids.length] = monitor.Id;
|
||||
}
|
||||
|
||||
var url = Servers[serverId].urlToApi()+'/events/index';
|
||||
$j('#fieldsTable input,#fieldsTable select').each(function(index) {
|
||||
const el = $j(this);
|
||||
const val = el.val();
|
||||
if (val && (!Array.isArray(val) || val.length)) {
|
||||
const name = el.attr('name');
|
||||
|
||||
if (name) {
|
||||
const found = name.match(/filter\[Query\]\[terms\]\[(\d)+\]\[val\]/);
|
||||
if (found) {
|
||||
const attr_name = 'filter[Query][terms]['+found[1]+'][attr]';
|
||||
const attr = this.form.elements[attr_name];
|
||||
const op_name = 'filter[Query][terms]['+found[1]+'][op]';
|
||||
const op = this.form.elements[op_name];
|
||||
if (attr) {
|
||||
url += '/'+attr.value+' '+op.value+':'+encodeURIComponent(val);
|
||||
} else {
|
||||
console.log('No attr for '+attr_name);
|
||||
}
|
||||
//} else {
|
||||
//console.log("No match for " + name);
|
||||
}
|
||||
data[name] = val;
|
||||
const cookie = el.attr('data-cookie');
|
||||
if (cookie) setCookie(cookie, val, 3600);
|
||||
} // end if name
|
||||
} // end if val
|
||||
});
|
||||
|
||||
function receive_events(data) {
|
||||
if (data.result == 'Error') {
|
||||
alert(data.message);
|
||||
return;
|
||||
}
|
||||
if (!data.events) {
|
||||
console.log(data);
|
||||
return;
|
||||
}
|
||||
console.log("Event data ", data);
|
||||
|
||||
if (data.events.length) {
|
||||
const event_list = {};
|
||||
for (let i=0, len = data.events.length; i<len; i++) {
|
||||
const ev = data.events[i].Event;
|
||||
events[parseInt(ev.Id)] = ev;
|
||||
if (!events_by_monitor_id[ev.MonitorId]) {
|
||||
events_by_monitor_id[ev.MonitorId] = []; // just event ids
|
||||
events_for_monitor[ev.MonitorId] = []; // id=>event
|
||||
}
|
||||
events_by_monitor_id[ev.MonitorId].push(ev.Id);
|
||||
events_for_monitor[ev.MonitorId].push(ev);
|
||||
drawEventOnGraph(ev);
|
||||
event_list[ev.Id] = ev;
|
||||
events[ev.id] = ev;
|
||||
}
|
||||
loadFrames(event_list);
|
||||
}
|
||||
} // end function receive_events
|
||||
|
||||
if (ajax) ajax.abort();
|
||||
LOADING = false;
|
||||
|
||||
if (mon_ids.length) {
|
||||
for (let i=0; i < mon_ids.length; i++) {
|
||||
ajax = $j.ajax({
|
||||
url: url+ '/MonitorId:'+mon_ids[i]+ '.json'+'?'+auth_relay,
|
||||
method: 'GET',
|
||||
//url: thisUrl + '?view=request&request=events&task=query&sort=Id&order=ASC',
|
||||
//data: data,
|
||||
timeout: 0,
|
||||
success: receive_events,
|
||||
error: function(jqXHR) {
|
||||
ajax = null;
|
||||
console.log("error", jqXHR);
|
||||
//logAjaxFail(jqXHR);
|
||||
//$j('#eventTable').bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
} // end foreach monitor
|
||||
} else {
|
||||
ajax = $j.ajax({
|
||||
url: url+'.json'+'?'+auth_relay,
|
||||
method: 'GET',
|
||||
//url: thisUrl + '?view=request&request=events&task=query&sort=Id&order=ASC',
|
||||
//data: data,
|
||||
timeout: 0,
|
||||
success: receive_events,
|
||||
error: function(jqXHR) {
|
||||
ajax = null;
|
||||
console.log("error", jqXHR);
|
||||
//logAjaxFail(jqXHR);
|
||||
//$j('#eventTable').bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
} // end function loadEventData
|
||||
|
||||
function initPage() {
|
||||
if (!liveMode) {
|
||||
load_Frames(events);
|
||||
canvas = document.getElementById('timeline');
|
||||
|
||||
canvas.addEventListener('mousemove', mmove, false);
|
||||
@@ -1041,59 +1202,39 @@ function initPage() {
|
||||
canvas.addEventListener('mouseout', mout, false);
|
||||
|
||||
ctx = canvas.getContext('2d', {willReadFrequently: true});
|
||||
|
||||
// draw an empty timeline
|
||||
drawGraph();
|
||||
}
|
||||
|
||||
for ( let i = 0, len = monitorPtr.length; i < len; i += 1 ) {
|
||||
for (let i = 0, len = monitorPtr.length; i < len; i += 1) {
|
||||
const monId = monitorPtr[i];
|
||||
if (!monId) continue;
|
||||
monitorCanvasObj[monId] = document.getElementById('Monitor'+monId);
|
||||
if ( !monitorCanvasObj[monId] ) {
|
||||
alert("Couldn't find DOM element for Monitor" + monId + "monitorPtr.length=" + len);
|
||||
} else {
|
||||
monitorCanvasCtx[monId] = monitorCanvasObj[monId].getContext('2d');
|
||||
const imageObject = monitorImageObject[monId] = new Image();
|
||||
imageObject.monId = monId;
|
||||
imageObject.onload = function() {
|
||||
imagedone(this, this.monId, true);
|
||||
};
|
||||
imageObject.onerror = function() {
|
||||
imagedone(this, this.monId, false);
|
||||
};
|
||||
loadImage2Monitor(monId, monitorImageURL[monId]);
|
||||
monitorCanvasObj[monId].addEventListener('click', clickMonitor, false);
|
||||
continue;
|
||||
}
|
||||
monitorCanvasCtx[monId] = monitorCanvasObj[monId].getContext('2d');
|
||||
const imageObject = monitorImageObject[monId] = new Image();
|
||||
imageObject.monId = monId;
|
||||
imageObject.onload = function() {
|
||||
imagedone(this, this.monId, true);
|
||||
};
|
||||
imageObject.onerror = function() {
|
||||
imagedone(this, this.monId, false);
|
||||
};
|
||||
if (liveMode) loadImage2Monitor(monId, monitorImageURL[monId]);
|
||||
monitorCanvasObj[monId].addEventListener('click', clickMonitor, false);
|
||||
} // end foreach monitor
|
||||
|
||||
setSpeed(speedIndex);
|
||||
//setFit(fitMode); // will redraw
|
||||
//setLive(liveMode); // will redraw
|
||||
loadEventData();
|
||||
wait_for_events();
|
||||
redrawScreen();
|
||||
/*
|
||||
$j('#minTime').datetimepicker({
|
||||
timeFormat: "HH:mm:ss",
|
||||
dateFormat: "yy-mm-dd",
|
||||
maxDate: +0,
|
||||
constrainInput: false,
|
||||
onClose: function(newDate, oldData) {
|
||||
if (newDate !== oldData.lastVal) {
|
||||
changeDateTime();
|
||||
}
|
||||
}
|
||||
});
|
||||
$j('#maxTime').datetimepicker({
|
||||
timeFormat: "HH:mm:ss",
|
||||
dateFormat: "yy-mm-dd",
|
||||
minDate: minTime,
|
||||
maxDate: +0,
|
||||
constrainInput: false,
|
||||
onClose: function(newDate, oldData) {
|
||||
if ( newDate !== oldData.lastVal ) {
|
||||
changeDateTime();
|
||||
}
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
$j('#scaleslider').bind('change', function() {
|
||||
setScale(this.value);
|
||||
});
|
||||
@@ -1119,15 +1260,26 @@ function initPage() {
|
||||
$j('#fieldsTable input, #fieldsTable select').each(function(index) {
|
||||
const el = $j(this);
|
||||
if (el.hasClass('datetimepicker')) {
|
||||
el.datetimepicker({timeFormat: "HH:mm:ss", dateFormat: "yy-mm-dd", maxDate: 0, constrainInput: false, onClose: changeDateTime});
|
||||
el.datetimepicker({timeFormat: "HH:mm:ss", dateFormat: "yy-mm-dd", maxDate: 0, constrainInput: false, onClose: changeFilters});
|
||||
} else if (el.hasClass('datepicker')) {
|
||||
el.datepicker({dateFormat: "yy-mm-dd", maxDate: 0, constrainInput: false, onClose: changeDateTime});
|
||||
el.datepicker({dateFormat: "yy-mm-dd", maxDate: 0, constrainInput: false, onClose: changeFilters});
|
||||
} else {
|
||||
el.on('change', changeDateTime);
|
||||
el.on('change', changeFilters);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function wait_for_events() {
|
||||
if (Object.keys(events).length === 0) {
|
||||
if (!wait_for_events_interval)
|
||||
wait_for_events_interval = setInterval(wait_for_events, 1000);
|
||||
} else {
|
||||
clearInterval(wait_for_events_interval);
|
||||
wait_for_events_interval = null;
|
||||
timerFire();
|
||||
}
|
||||
}
|
||||
|
||||
function takeSnapshot() {
|
||||
monitor_ids = [];
|
||||
for (const key in monitorIndex) {
|
||||
@@ -1156,8 +1308,7 @@ window.addEventListener("resize", redrawScreen, {passive: true});
|
||||
window.addEventListener('DOMContentLoaded', initPage);
|
||||
|
||||
/* Expects and Object, not an array, of EventId=>Event mappings. */
|
||||
function load_Frames(zm_events) {
|
||||
console.log("Loading frames", zm_events);
|
||||
function loadFrames(zm_events) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const url = Servers[serverId].urlToApi()+'/frames/index';
|
||||
|
||||
@@ -1219,4 +1370,4 @@ function load_Frames(zm_events) {
|
||||
} // end while zm_events.legtnh
|
||||
} // end Promise
|
||||
);
|
||||
} // end function load_Frames(Event)
|
||||
} // end function loadFrames(Event)
|
||||
|
||||
@@ -31,6 +31,7 @@ var fitMode=<?php echo $fitMode?>;
|
||||
// slider scale, which is only for replay and relative to real time
|
||||
var currentSpeed=<?php echo $speeds[$speedIndex]?>;
|
||||
var speedIndex=<?php echo $speedIndex?>;
|
||||
var lastSpeedIndex=0;
|
||||
|
||||
// will be set based on performance, this is the display interval in milliseconds
|
||||
// for history, and fps for live, and dynamically determined (in ms)
|
||||
@@ -58,6 +59,7 @@ if (!$liveMode) {
|
||||
echo "const events = {\n";
|
||||
$EventsById = array();
|
||||
|
||||
if (0) {
|
||||
$result = dbQuery($eventsSql);
|
||||
if ($result) {
|
||||
while ( $event = $result->fetch(PDO::FETCH_ASSOC) ) {
|
||||
@@ -67,14 +69,15 @@ if (!$liveMode) {
|
||||
|
||||
$events_by_monitor_id = array();
|
||||
|
||||
$eventMaxSecs = 0;
|
||||
foreach ($EventsById as $event_id=>$event) {
|
||||
|
||||
$StartTimeSecs = $event['StartTimeSecs'];
|
||||
$EndTimeSecs = $event['EndTimeSecs'];
|
||||
|
||||
# It isn't neccessary to do this for each event. We should be able to just look at the first and last
|
||||
if ( !$minTimeSecs or $minTimeSecs > $StartTimeSecs ) $minTimeSecs = $StartTimeSecs;
|
||||
if ( !$maxTimeSecs or $maxTimeSecs < $EndTimeSecs ) $maxTimeSecs = $EndTimeSecs;
|
||||
if ($StartTimeSecs > $eventMaxSecs) $eventMaxSecs = $StartTimeSecs;
|
||||
|
||||
$event_json = json_encode($event, JSON_PRETTY_PRINT|JSON_NUMERIC_CHECK);
|
||||
echo " $event_id : $event_json,\n";
|
||||
@@ -89,10 +92,12 @@ if (!$liveMode) {
|
||||
$events_by_monitor_id[$event['MonitorId']] = array();
|
||||
array_push($events_by_monitor_id[$event['MonitorId']], $event_id);
|
||||
} # end foreach Event
|
||||
if ($eventMaxSecs < $maxTimeSecs) $maxTimeSecs = $eventMaxSecs;
|
||||
}
|
||||
echo ' };
|
||||
|
||||
const events_for_monitor = [];
|
||||
const events_by_monitor_id = '.json_encode($events_by_monitor_id, JSON_NUMERIC_CHECK).PHP_EOL;
|
||||
const events_for_monitor = {};
|
||||
const events_by_monitor_id = {};'; #.json_encode($events_by_monitor_id, JSON_NUMERIC_CHECK).PHP_EOL;
|
||||
|
||||
// if there is no data set the min/max to the passed in values
|
||||
if ( $index == 0 ) {
|
||||
@@ -129,6 +134,28 @@ if ( !$have_storage_zero ) {
|
||||
$Storage = new ZM\Storage();
|
||||
echo 'Storage[0] = ' . $Storage->to_json(). ";\n";
|
||||
}
|
||||
echo "\nconst monitorData = [];\n";
|
||||
foreach ( $monitors as $monitor ) {
|
||||
if ($monitor->Deleted() or !$monitor->canView()) continue;
|
||||
?>
|
||||
|
||||
monitorData[monitorData.length] = {
|
||||
'Id': <?php echo $monitor->Id() ?>,
|
||||
'Name': '<?php echo $monitor->Name() ?>',
|
||||
'connKey': '<?php echo $monitor->connKey() ?>',
|
||||
'Width': <?php echo $monitor->ViewWidth() ?>,
|
||||
'Height':<?php echo $monitor->ViewHeight() ?>,
|
||||
'JanusEnabled':<?php echo $monitor->JanusEnabled() ?>,
|
||||
'Url': '<?php echo $monitor->UrlToIndex( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
|
||||
'UrlToZms': '<?php echo $monitor->UrlToZMS( ZM_MIN_STREAMING_PORT ? ($monitor->Id() + ZM_MIN_STREAMING_PORT) : '') ?>',
|
||||
'onclick': function(){window.location.assign( '?view=watch&mid=<?php echo $monitor->Id() ?>' );},
|
||||
'Type': '<?php echo $monitor->Type() ?>',
|
||||
'Refresh': '<?php echo $monitor->Refresh() ?>',
|
||||
'Janus_Pin': '<?php echo $monitor->Janus_Pin() ?>',
|
||||
'WebColour': '<?php echo $monitor->WebColour() ?>'
|
||||
};
|
||||
<?php
|
||||
} // end foreach monitor
|
||||
|
||||
echo '
|
||||
var monitorName = [];
|
||||
@@ -183,16 +210,17 @@ foreach ( $monitors as $m ) {
|
||||
}
|
||||
echo "
|
||||
var numMonitors = $numMonitors;
|
||||
var minTimeSecs=parseInt($minTimeSecs);
|
||||
var maxTimeSecs=parseInt($maxTimeSecs);
|
||||
var minTimeSecs =parseInt($minTimeSecs);
|
||||
var maxTimeSecs =parseInt($maxTimeSecs);
|
||||
var minTime='$minTime';
|
||||
var maxTime='$maxTime';
|
||||
";
|
||||
echo 'var rangeTimeSecs='.($maxTimeSecs - $minTimeSecs + 1).";\n";
|
||||
if ( isset($defaultCurrentTimeSecs) )
|
||||
if ( isset($defaultCurrentTimeSecs) ) {
|
||||
echo 'var currentTimeSecs=parseInt('.$defaultCurrentTimeSecs.");\n";
|
||||
else
|
||||
} else {
|
||||
echo 'var currentTimeSecs=parseInt('.$minTimeSecs.");\n";
|
||||
}
|
||||
|
||||
echo 'var speeds=[';
|
||||
for ( $i=0; $i < count($speeds); $i++ )
|
||||
@@ -202,6 +230,7 @@ echo "];\n";
|
||||
|
||||
var cWidth; // save canvas width
|
||||
var cHeight; // save canvas height
|
||||
var rowHeight = 0;
|
||||
var canvas; // global canvas definition so we don't have to keep looking it up
|
||||
var ctx = null;
|
||||
var underSlider; // use this to hold what is hidden by the slider
|
||||
|
||||
Reference in New Issue
Block a user