mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-05-19 03:56:18 -04:00
Merge branch 'master' into patch-435415
This commit is contained in:
@@ -116,7 +116,11 @@ jobs:
|
||||
run: |
|
||||
set -eux
|
||||
mkdir -p artifacts/deb
|
||||
mv ../*.deb ../*.buildinfo ../*.changes ../*.dsc ../*.tar.xz ../*.tar.gz artifacts/deb/ || true
|
||||
# Only collect binary artifacts. Source files (.dsc, .orig.tar.*,
|
||||
# .debian.tar.*) are not needed for binary uploads and the orig
|
||||
# tarball has the same filename across distros which confuses
|
||||
# mini-dinstall.
|
||||
mv ../*.deb ../*.buildinfo ../*.changes artifacts/deb/ || true
|
||||
# quick verify signatures (non-fatal)
|
||||
gpg --verify artifacts/deb/*.changes || true
|
||||
gpg --verify artifacts/deb/*.buildinfo || true
|
||||
|
||||
6
.github/workflows/build-deb-packages.yml
vendored
6
.github/workflows/build-deb-packages.yml
vendored
@@ -112,7 +112,11 @@ jobs:
|
||||
set -eux
|
||||
mkdir -p artifacts/deb
|
||||
ls -l ../
|
||||
mv ../*.deb ../*.buildinfo ../*.changes ../*.dsc ../*.tar.xz ../*.tar.gz artifacts/deb/ || true
|
||||
# Only collect binary artifacts. Source files (.dsc, .orig.tar.*,
|
||||
# .debian.tar.*) are not needed for binary uploads and the orig
|
||||
# tarball has the same filename across distros which confuses
|
||||
# mini-dinstall.
|
||||
mv ../*.deb ../*.buildinfo ../*.changes artifacts/deb/ || true
|
||||
# quick verify signatures (non-fatal)
|
||||
gpg --verify artifacts/deb/*.changes || true
|
||||
gpg --verify artifacts/deb/*.buildinfo || true
|
||||
|
||||
@@ -98,22 +98,24 @@ sub Execute {
|
||||
$sql =~ s/zmSystemLoad/$load/g;
|
||||
}
|
||||
|
||||
|
||||
Debug("Filter::Execute SQL ($sql)");
|
||||
my $sth = $ZoneMinder::Database::dbh->prepare($sql)
|
||||
or Fatal("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr());
|
||||
my $sth = $ZoneMinder::Database::dbh->prepare($sql);
|
||||
if (!$sth) {
|
||||
Error("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr());
|
||||
return;
|
||||
}
|
||||
my $res = $sth->execute();
|
||||
if ( !$res ) {
|
||||
Error("Can't execute filter '$sql', ignoring: ".$sth->errstr());
|
||||
return;
|
||||
}
|
||||
Debug("Filter::Execute SQL ($sql)");
|
||||
my @results;
|
||||
while ( my $event = $sth->fetchrow_hashref() ) {
|
||||
push @results, $event;
|
||||
}
|
||||
$sth->finish();
|
||||
Debug('Loaded ' . @results . ' events for filter '.$$self{Name}.' using query ('.$sql.')"');
|
||||
if ( $self->{PostSQLConditions} ) {
|
||||
if ($self->{PostSQLConditions} and @{$self->{PostSQLConditions}}) {
|
||||
my @filtered_events;
|
||||
foreach my $term ( @{$$self{PostSQLConditions}} ) {
|
||||
if ( $$term{attr} eq 'ExistsInFileSystem' ) {
|
||||
|
||||
@@ -76,6 +76,7 @@ our %EXPORT_TAGS = (
|
||||
Dump
|
||||
Debug
|
||||
Info
|
||||
Warn
|
||||
Warning
|
||||
Error
|
||||
Fatal
|
||||
@@ -741,6 +742,7 @@ sub info {
|
||||
$log->logPrint(INFO, @_, caller);
|
||||
}
|
||||
|
||||
sub Warn { fetch()->logPrint(WARNING, @_, caller); }
|
||||
sub Warning { fetch()->logPrint(WARNING, @_, caller); }
|
||||
sub warn {
|
||||
my $log = shift;
|
||||
|
||||
@@ -347,12 +347,11 @@ sub control {
|
||||
my $command = shift;
|
||||
my $process = shift;
|
||||
|
||||
my $valid_device = (defined $monitor->{Device} and $monitor->{Device} =~ /^\/dev\/[\w\/.\-]+$/);
|
||||
if ($monitor->{Type} eq 'Local' and !$valid_device) {
|
||||
Error("Invalid device path rejected: $monitor->{Device}");
|
||||
return;
|
||||
} elsif (!$valid_device and defined $monitor->{Device} and length($monitor->{Device})) {
|
||||
Warning("Monitor $$monitor{Id} has invalid device path: $monitor->{Device}");
|
||||
if ($monitor->{Type} eq 'Local') {
|
||||
if (!defined $monitor->{Device} or $monitor->{Device} !~ /^\/dev\/[\w\/.\-]+$/) {
|
||||
Error("Invalid device path rejected: $monitor->{Device}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($command eq 'stop') {
|
||||
|
||||
@@ -127,7 +127,15 @@ sub CpuUsage {
|
||||
|
||||
} else {
|
||||
# Get CPU utilization percentages
|
||||
my $top_output = `top -b -n 1 | grep -i "^%Cpu(s)" | awk '{print \$2, \$4, \$6, \$8}'`;
|
||||
my $top_output = '';
|
||||
my $uname_output = lc(qx($ZoneMinder::Config{ZM_PATH_UNAME} -s));
|
||||
chomp($uname_output);
|
||||
## FreeBSD
|
||||
if ($uname_output eq "freebsd") {
|
||||
$top_output = `top -b -n 1 | grep "^CPU" | sed 's/%//g' | awk '{print \$2, \$6, \$4, \$10}'`;
|
||||
} else {
|
||||
$top_output = `top -b -n 1 | grep -i "^%Cpu(s)" | awk '{print \$2, \$4, \$6, \$8}'`;
|
||||
}
|
||||
my ($user, $system, $nice, $idle) = split(/ /, $top_output);
|
||||
$user =~ s/[^\d\.]//g;
|
||||
$system =~ s/[^\d\.]//g;
|
||||
|
||||
@@ -277,9 +277,16 @@ sub run {
|
||||
die 'Can\'t open pid file at '.ZM_PID."\n";
|
||||
}
|
||||
|
||||
my $fd = 0;
|
||||
# Redirect stdin/stdout/stderr to /dev/null rather than closing them.
|
||||
# Closing them causes the FDs to be reused, which led to memory corruption
|
||||
# when libx264 wrote to a reused stderr FD (66f11435b). Redirecting to
|
||||
# /dev/null keeps valid FDs that children inherit safely.
|
||||
open(STDIN, '<', '/dev/null') or die "Can't redirect STDIN: $!";
|
||||
open(STDOUT, '>', '/dev/null') or die "Can't redirect STDOUT: $!";
|
||||
open(STDERR, '>', '/dev/null') or die "Can't redirect STDERR: $!";
|
||||
|
||||
# This also closes dbh and CLIENT and SERVER
|
||||
# Close all remaining FDs (dbh, CLIENT, SERVER, etc.)
|
||||
my $fd = 3;
|
||||
while ( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) {
|
||||
POSIX::close($fd++);
|
||||
}
|
||||
|
||||
@@ -161,28 +161,19 @@ if ( ! ( $filter_name or $filter_id ) ) {
|
||||
}
|
||||
|
||||
my @filters;
|
||||
my $last_action = 0;
|
||||
my $last_reload = 0;
|
||||
|
||||
while (!$zm_terminate) {
|
||||
my $delay;
|
||||
my $now = time;
|
||||
if (($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY}) {
|
||||
Debug('Reloading filters');
|
||||
$last_action = $now;
|
||||
if (($now - $last_reload) > $Config{ZM_FILTER_RELOAD_DELAY}) {
|
||||
$last_reload = $now;
|
||||
@filters = getFilters({ Name=>$filter_name, Id=>$filter_id });
|
||||
}
|
||||
|
||||
foreach my $filter (@filters) {
|
||||
last if $zm_terminate;
|
||||
|
||||
my $elapsed = ($now - ($$filter{last_ran} ? $$filter{last_ran} : 0));
|
||||
my $filter_delay = $$filter{ExecuteInterval} - $elapsed;
|
||||
Warn("Filter $$filter{Name} is taking ".(-$filter_delay)." seconds longer than execute interval.") if $filter_delay < 0;
|
||||
if (!defined($delay) or $filter_delay < $delay) {
|
||||
$delay = $filter_delay > 0 ? $filter_delay : 0;
|
||||
Debug("Setting delay to $delay because ExecuteInterval=$$filter{ExecuteInterval} and $elapsed have elapsed for $$filter{Name}");
|
||||
}
|
||||
|
||||
if ($$filter{Concurrent} and !($filter_id or $filter_name)) {
|
||||
my ( $proc ) = $0 =~ /(\S+)/;
|
||||
my ( $id ) = $$filter{Id} =~ /(\d+)/;
|
||||
@@ -193,6 +184,15 @@ while (!$zm_terminate) {
|
||||
checkFilter($filter);
|
||||
$$filter{last_ran} = $now;
|
||||
}
|
||||
|
||||
$now = time;
|
||||
my $elapsed = ($now - ($$filter{last_ran} ? $$filter{last_ran} : $now));
|
||||
my $filter_delay = $$filter{ExecuteInterval} - $elapsed;
|
||||
Warning("Filter $$filter{Name} is taking ".(-$filter_delay)." seconds longer than execute interval $$filter{ExecuteInterval}.") if $filter_delay < 0;
|
||||
if (!defined($delay) or $filter_delay < $delay) {
|
||||
$delay = $filter_delay > 0 ? $filter_delay : 0;
|
||||
Debug("Setting delay to $delay because ExecuteInterval=$$filter{ExecuteInterval} and $elapsed have elapsed for $$filter{Name}");
|
||||
}
|
||||
} # end foreach filter
|
||||
|
||||
last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate;
|
||||
|
||||
@@ -2720,9 +2720,12 @@ void Image::Fill(Rgb colour, int density, const Polygon &polygon) {
|
||||
std::sort(active_edges.begin(), active_edges.end(), PolygonFill::Edge::CompareX);
|
||||
|
||||
if (!(scan_line % density)) {
|
||||
for (auto it = active_edges.begin(); it < active_edges.end() - 1; ++it) {
|
||||
// Fill between pairs of active edges (parity rule). Stepping one
|
||||
// edge at a time would incorrectly fill the gaps between arms of
|
||||
// a non-convex polygon (e.g. a banana shape).
|
||||
for (auto it = active_edges.begin(); it + 1 < active_edges.end(); it += 2) {
|
||||
int32 lo_x = static_cast<int32>(it->min_x);
|
||||
int32 hi_x = static_cast<int32>(std::next(it)->min_x);
|
||||
int32 hi_x = static_cast<int32>((it + 1)->min_x);
|
||||
if (colours == ZM_COLOUR_GRAY8) {
|
||||
uint8 *p = &buffer[(scan_line * width) + lo_x];
|
||||
|
||||
|
||||
@@ -676,8 +676,8 @@ int LocalCamera::Initialise() {
|
||||
if (palette == V4L2_PIX_FMT_JPEG || palette == V4L2_PIX_FMT_MJPEG) {
|
||||
v4l2_jpegcompression jpeg_comp;
|
||||
if (vidioctl(vid_fd, VIDIOC_G_JPEGCOMP, &jpeg_comp) < 0) {
|
||||
if (errno == EINVAL) {
|
||||
Debug(2, "JPEG compression options are not available");
|
||||
if (errno == EINVAL || errno == ENOTTY) {
|
||||
Debug(2, "JPEG compression options are not available: %s", strerror(errno));
|
||||
} else {
|
||||
Warning("Failed to get JPEG compression options: %s", strerror(errno));
|
||||
}
|
||||
|
||||
@@ -3708,6 +3708,7 @@ int Monitor::Pause() {
|
||||
convert_context = nullptr;
|
||||
}
|
||||
decoding_image_count = 0;
|
||||
if (shared_data) shared_data->last_write_index = image_buffer_count;
|
||||
}
|
||||
if (analysis_thread) {
|
||||
Debug(1, "Joining analysis");
|
||||
|
||||
@@ -85,6 +85,9 @@ bool Monitor::MonitorLink::connect() {
|
||||
if (!last_connect_time || (now - std::chrono::system_clock::from_time_t(last_connect_time)) > Seconds(1)) {
|
||||
last_connect_time = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
// Clean up any existing resources before reconnecting to avoid fd leaks
|
||||
disconnect();
|
||||
|
||||
mem_size = sizeof(SharedData) + sizeof(TriggerData);
|
||||
|
||||
Debug(1, "link.mem.size=%jd", static_cast<intmax_t>(mem_size));
|
||||
@@ -160,27 +163,25 @@ bool Monitor::MonitorLink::connect() {
|
||||
} // end bool Monitor::MonitorLink::connect()
|
||||
|
||||
bool Monitor::MonitorLink::disconnect() {
|
||||
if (connected) {
|
||||
connected = false;
|
||||
connected = false;
|
||||
|
||||
#if ZM_MEM_MAPPED
|
||||
if (mem_ptr > (void *)0) {
|
||||
msync(mem_ptr, mem_size, MS_ASYNC);
|
||||
munmap(mem_ptr, mem_size);
|
||||
}
|
||||
if (map_fd >= 0)
|
||||
close(map_fd);
|
||||
if (mem_ptr != nullptr && mem_ptr != MAP_FAILED) {
|
||||
msync(mem_ptr, mem_size, MS_ASYNC);
|
||||
munmap(mem_ptr, mem_size);
|
||||
}
|
||||
if (map_fd >= 0)
|
||||
close(map_fd);
|
||||
|
||||
map_fd = -1;
|
||||
map_fd = -1;
|
||||
#else // ZM_MEM_MAPPED
|
||||
if (mem_ptr != nullptr) {
|
||||
struct shmid_ds shm_data;
|
||||
if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) {
|
||||
Debug(3, "Can't shmctl: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
shm_id = 0;
|
||||
|
||||
if (shm_data.shm_nattch <= 1) {
|
||||
if (shmctl(shm_id, IPC_RMID, 0) < 0) {
|
||||
Debug(3, "Can't shmctl: %s", strerror(errno));
|
||||
@@ -192,10 +193,15 @@ bool Monitor::MonitorLink::disconnect() {
|
||||
Debug(3, "Can't shmdt: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
#endif // ZM_MEM_MAPPED
|
||||
mem_size = 0;
|
||||
mem_ptr = nullptr;
|
||||
}
|
||||
shm_id = 0;
|
||||
#endif // ZM_MEM_MAPPED
|
||||
mem_size = 0;
|
||||
mem_ptr = nullptr;
|
||||
shared_data = nullptr;
|
||||
trigger_data = nullptr;
|
||||
zone_scores = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ VideoStore::VideoStore(
|
||||
audio_out_ctx(nullptr),
|
||||
packets_written(0),
|
||||
frame_count(0),
|
||||
video_encoded(false),
|
||||
hw_device_ctx(nullptr),
|
||||
resample_ctx(nullptr),
|
||||
fifo(nullptr),
|
||||
@@ -239,12 +240,21 @@ bool VideoStore::open() {
|
||||
if ((ret = avcodec_open2(video_out_ctx, video_out_codec, &opts)) < 0) {
|
||||
Warning("Can't open video codec (%s) %s", video_out_codec->name, av_make_error_string(ret).c_str());
|
||||
video_out_codec = nullptr;
|
||||
avcodec_free_context(&video_out_ctx);
|
||||
}
|
||||
} // end if video_out_codec
|
||||
|
||||
ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx);
|
||||
if (ret < 0) {
|
||||
Error("Could not initialize stream parameters");
|
||||
if (video_out_ctx) {
|
||||
ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx);
|
||||
if (ret < 0) {
|
||||
Error("Could not initialize stream parameters");
|
||||
}
|
||||
// Free the codec context now — it was only opened to generate new
|
||||
// extradata for the stream parameters and is not used for encoding
|
||||
// in PASSTHROUGH mode. Leaving it alive causes flush_codecs() to
|
||||
// attempt flushing a codec that never received any frames, which
|
||||
// crashes in avcodec_send_frame with newer FFmpeg.
|
||||
avcodec_free_context(&video_out_ctx);
|
||||
}
|
||||
av_dict_free(&opts);
|
||||
// Reload it for next attempt and/or avformat open
|
||||
@@ -345,6 +355,11 @@ bool VideoStore::open() {
|
||||
}
|
||||
if (setup_hwaccel(video_out_ctx,
|
||||
chosen_codec_data, hw_device_ctx, monitor->EncoderHWAccelDevice(), monitor->Width(), monitor->Height())) {
|
||||
avcodec_free_context(&video_out_ctx);
|
||||
av_dict_free(&opts);
|
||||
if (hw_device_ctx) {
|
||||
av_buffer_unref(&hw_device_ctx);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -566,7 +581,9 @@ void VideoStore::flush_codecs() {
|
||||
}
|
||||
|
||||
// I got crashes if the codec didn't do DELAY, so let's test for it.
|
||||
if (video_out_ctx && video_out_ctx->codec && (video_out_ctx->codec->capabilities & AV_CODEC_CAP_DELAY)) {
|
||||
// Also skip if no frames were ever sent — some encoders crash on flush
|
||||
// when their internal state was never initialized by a real frame.
|
||||
if (video_out_ctx && video_encoded && video_out_ctx->codec && (video_out_ctx->codec->capabilities & AV_CODEC_CAP_DELAY)) {
|
||||
// First drain any pending packets before entering flush mode
|
||||
// This prevents hangs when the encoder's internal buffer is full
|
||||
Debug(1, "Draining pending packets before flush");
|
||||
@@ -1297,6 +1314,7 @@ int VideoStore::writeVideoFramePacket(const std::shared_ptr<ZMPacket> zm_packet)
|
||||
Debug(3, "Got EAGAIN");
|
||||
}
|
||||
} else {
|
||||
video_encoded = true;
|
||||
break;
|
||||
}
|
||||
} while (!zm_terminate);
|
||||
|
||||
@@ -54,6 +54,7 @@ class VideoStore {
|
||||
SWScale swscale;
|
||||
unsigned int packets_written;
|
||||
unsigned int frame_count;
|
||||
bool video_encoded; // true once at least one frame has been sent to the video encoder
|
||||
|
||||
AVBufferRef *hw_device_ctx;
|
||||
|
||||
|
||||
@@ -303,7 +303,11 @@ int main(int argc, char *argv[]) {
|
||||
time_t last_viewed = monitors[i]->getLastViewed();
|
||||
int64 since_last_view = static_cast<int64>(std::chrono::duration_cast<Seconds>(now.time_since_epoch()).count()) - last_viewed;
|
||||
Debug(1, "Last view %jd= %" PRId64 " seconds since last view", last_viewed, since_last_view);
|
||||
if (((!last_viewed) or (since_last_view > 10)) and (monitors[i]->GetLastWriteIndex() != -1)) {
|
||||
if (!last_viewed or (since_last_view > 10)) {
|
||||
// Nobody is watching — pause if running, otherwise stay paused.
|
||||
// The previous GetLastWriteIndex() != -1 guard caused a
|
||||
// Pause/Play cycle because Pause() resets the write index,
|
||||
// making the guard false and falling through to Play().
|
||||
if (monitors[i]->getCamera()->isPrimed()) {
|
||||
monitors[i]->Pause();
|
||||
}
|
||||
|
||||
@@ -315,7 +315,7 @@ EOF
|
||||
sudo apt-get install devscripts equivs
|
||||
sudo mk-build-deps -ir $DIRECTORY.orig/debian/control
|
||||
echo "Status: $?"
|
||||
DEBUILD=debuild -b -uc -us
|
||||
DEBUILD="debuild -b -uc -us"
|
||||
else
|
||||
if [ $TYPE == "local" ]; then
|
||||
# Auto-install all ZoneMinder's dependencies using the Debian control file
|
||||
|
||||
@@ -63,7 +63,7 @@ class Monitor extends AppModel {
|
||||
),
|
||||
'Device' => array(
|
||||
'validPath' => array(
|
||||
'rule' => array('custom', '#^(/dev/[\w/.\-]+)?$#'),
|
||||
'rule' => array('validDevicePath'),
|
||||
'message' => 'Invalid device path. Must be a valid /dev/ path (e.g. /dev/video0).',
|
||||
'allowEmpty' => true,
|
||||
'required' => false,
|
||||
@@ -72,6 +72,23 @@ class Monitor extends AppModel {
|
||||
|
||||
);
|
||||
|
||||
/**
|
||||
* Validate the Device field. Only Local monitors pass Device to a shell,
|
||||
* so the /dev/ restriction only applies when Type == 'Local'. Other Types
|
||||
* may legitimately hold legacy values (e.g. an RTSP URL) in this column.
|
||||
*/
|
||||
public function validDevicePath($check) {
|
||||
$value = reset($check);
|
||||
if ($value === null || $value === '') {
|
||||
return true;
|
||||
}
|
||||
$type = isset($this->data['Monitor']['Type']) ? $this->data['Monitor']['Type'] : null;
|
||||
if ($type !== 'Local') {
|
||||
return true;
|
||||
}
|
||||
return (bool)preg_match('#^/dev/[\w/.\-]+$#', $value);
|
||||
}
|
||||
|
||||
//The Associations below have been created with all possible keys, those that are not needed can be removed
|
||||
|
||||
/**
|
||||
|
||||
@@ -58,8 +58,10 @@ if ($action == 'save') {
|
||||
# For convenience
|
||||
$newMonitor = $_REQUEST['newMonitor'];
|
||||
|
||||
# Validate Device path to prevent command injection (CVE-worthy)
|
||||
if (!empty($newMonitor['Device'])) {
|
||||
# Validate Device path to prevent command injection (CVE-worthy).
|
||||
# Only Local monitors pass Device to a shell; for other Types the field
|
||||
# is unused and may legitimately hold legacy values (e.g. an RTSP URL).
|
||||
if (!empty($newMonitor['Device']) and isset($newMonitor['Type']) and $newMonitor['Type'] == 'Local') {
|
||||
$newMonitor['Device'] = validDevicePath($newMonitor['Device']);
|
||||
if ($newMonitor['Device'] === '') {
|
||||
$error_message .= 'Invalid device path. Must be a valid /dev/ path (e.g. /dev/video0).</br>';
|
||||
|
||||
@@ -2378,7 +2378,7 @@ function output_file($path, $chunkSize=1024) {
|
||||
$new_length = $size - $range;
|
||||
header('HTTP/1.1 206 Partial Content');
|
||||
header("Content-Length: $new_length");
|
||||
header("Content-Range: bytes $range$size2/$size");
|
||||
header("Content-Range: bytes $range-$size2/$size");
|
||||
} else {
|
||||
$size2 = $size - 1;
|
||||
header("Content-Range: bytes 0-$size2/$size");
|
||||
|
||||
@@ -189,7 +189,7 @@ function MonitorStream(monitorData) {
|
||||
this.show = function() {
|
||||
const stream = this.getElement();
|
||||
if (!stream.src) {
|
||||
stream.src = this.url_to_zms+"&mode=single&scale="+this.scale+"&connkey="+this.connKey+'&'+auth_relay;
|
||||
stream.src = this.url_to_zms+"&mode=single&scale="+this.scale+"&connkey="+this.connKey+(auth_relay?'&'+auth_relay:'');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -607,9 +607,9 @@ function MonitorStream(monitorData) {
|
||||
this.streamCommand(CMD_PLAY);
|
||||
} else {
|
||||
let src = this.url_to_zms.replace(/mode=single/i, 'mode=jpeg');
|
||||
if (-1 == src.search('auth')) {
|
||||
if (-1 == src.search('auth') && auth_relay) {
|
||||
src += '&'+auth_relay;
|
||||
} else {
|
||||
} else if (-1 != src.search('auth')) {
|
||||
src = src.replace(/auth=\w+/i, 'auth='+auth_hash);
|
||||
}
|
||||
if (-1 == src.search('connkey')) {
|
||||
@@ -650,9 +650,9 @@ function MonitorStream(monitorData) {
|
||||
if (!imgInfoBlock) return null;
|
||||
|
||||
let src = this.url_to_zms.replace(/mode=jpeg/i, 'mode=single');
|
||||
if (-1 == src.search('auth')) {
|
||||
if (-1 == src.search('auth') && auth_relay) {
|
||||
src += '&'+auth_relay;
|
||||
} else {
|
||||
} else if (-1 != src.search('auth')) {
|
||||
src = src.replace(/auth=\w+/i, 'auth='+auth_hash);
|
||||
}
|
||||
if (-1 == src.search('scale=')) {
|
||||
@@ -1511,7 +1511,7 @@ function MonitorStream(monitorData) {
|
||||
}; // this.getStatusCmdResponse
|
||||
|
||||
this.statusCmdQuery = function() {
|
||||
$j.getJSON(this.url + '?view=request&request=status&entity=monitor&element[]=Status&element[]=CaptureFPS&element[]=AnalysisFPS&element[]=Analysing&element[]=Recording&id='+this.id+'&'+auth_relay)
|
||||
$j.getJSON(this.url + '?view=request&request=status&entity=monitor&element[]=Status&element[]=CaptureFPS&element[]=AnalysisFPS&element[]=Analysing&element[]=Recording&id='+this.id+(auth_relay?'&'+auth_relay:''))
|
||||
.done(this.getStatusCmdResponse.bind(this))
|
||||
.fail(logAjaxFail);
|
||||
|
||||
|
||||
@@ -121,6 +121,11 @@ input[name="newStorage[Url]"] {
|
||||
margin-bottom: 20px;
|
||||
margin-right: 5px;
|
||||
border-bottom: 1px solid #e7e7e7;
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
#options .form-group span.form-text {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
#options .col-md {
|
||||
text-align: left;
|
||||
@@ -131,32 +136,34 @@ form {
|
||||
/* flex-direction: column;*/
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-left: 9px;
|
||||
/* padding-top: 2rem; */
|
||||
}
|
||||
|
||||
@media screen and (max-width:767px) {
|
||||
#options label,
|
||||
label.col-form-label {
|
||||
label.col-form-label {
|
||||
text-align: left;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
div.dnsmasq,
|
||||
.dnsmasq .config {
|
||||
text-align: left;
|
||||
text-align: left;
|
||||
}
|
||||
.dnsmasq .container {
|
||||
margin-left: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
.dnsmasq .config .row > label {
|
||||
width: 150px;
|
||||
text-align: right;
|
||||
width: 150px;
|
||||
text-align: right;
|
||||
}
|
||||
.dnsmasq .config .row {
|
||||
min-height:36px;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-content: space-around;
|
||||
min-height:36px;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
align-content: space-around;
|
||||
}
|
||||
#leasesTable td,
|
||||
#leasesTable th {
|
||||
@@ -197,7 +204,7 @@ body.sticky .fixed-table-container {
|
||||
}
|
||||
|
||||
body.sticky #controlTable thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
box-shadow: 0 0px 0, 0 -3px 0 #dfe4ea;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
box-shadow: 0 0px 0, 0 -3px 0 #dfe4ea;
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ if ( (null !== $filter->Concurrent()) and $filter->Concurrent() )
|
||||
<?php } ?>
|
||||
<p class="Name">
|
||||
<label for="filter[Name]"><?php echo translate('Name') ?></label>
|
||||
<input type="text" id="filter[Name]" name="filter[Name]" value="<?php echo validHtmlStr($filter->Name()) ?>" data-on-input-this="updateButtons"/>
|
||||
<input type="text" id="filter[Name]" name="filter[Name]" value="<?php echo validHtmlStr($filter->Name()) ?>" maxlength="64" data-on-input-this="updateButtons"/>
|
||||
</p>
|
||||
<?php
|
||||
if (ZM_OPT_USE_AUTH) {
|
||||
|
||||
@@ -368,7 +368,10 @@ function initPage() {
|
||||
|
||||
// Manage the SAVE Button
|
||||
document.getElementById("saveBtn").addEventListener("click", function onSaveClick(evt) {
|
||||
saveMonitorData();
|
||||
const form = document.getElementById('contentForm');
|
||||
if (validateForm(form)) {
|
||||
saveMonitorData();
|
||||
}
|
||||
});
|
||||
|
||||
// Manage the SAVE AND CLOSE Button - use AJAX instead of native form
|
||||
|
||||
@@ -63,6 +63,8 @@ function validateForm(form) {
|
||||
errors[errors.length] = "<?php echo translate('BadPalette') ?>";
|
||||
if ( !form.elements['newMonitor[Device]'].value )
|
||||
errors[errors.length] = "<?php echo translate('BadDevice') ?>";
|
||||
else if ( !form.elements['newMonitor[Device]'].value.match(/^\/dev\/[\w\/.\-]+$/) )
|
||||
errors[errors.length] = "<?php echo translate('BadDevice') ?>";
|
||||
if ( !form.elements['newMonitor[Channel]'] || !form.elements['newMonitor[Channel]'].value || !form.elements['newMonitor[Channel]'].value.match( /^\d+$/ ) )
|
||||
errors[errors.length] = "<?php echo translate('BadChannel') ?>";
|
||||
if ( !form.elements['newMonitor[Format]'] || !form.elements['newMonitor[Format]'].value || !form.elements['newMonitor[Format]'].value.match( /^\d+$/ ) )
|
||||
|
||||
@@ -668,7 +668,9 @@ switch ($name) {
|
||||
<label><?php echo translate('DevicePath') ?></label>
|
||||
<?php echo count($devices) > 1 ? htmlSelect('newMonitor[Devices]', $devices, $monitor->Device()) : ''; ?>
|
||||
<input type="text" name="newMonitor[Device]" value="<?php echo validHtmlStr($monitor->Device()) ?>"
|
||||
<?php echo (count($devices) > 1) ? 'style="display: none;"' : '' ?> autocomplete="off"
|
||||
<?php echo (count($devices) > 1 and $monitor->Device() != '') ? 'style="display: none;"' : '' ?> autocomplete="off"
|
||||
pattern="/dev/[\w\/.\-]+"
|
||||
title="<?php echo translate('BadDevice') ?> (e.g. /dev/video0)"
|
||||
/>
|
||||
</li>
|
||||
<?php
|
||||
|
||||
@@ -338,7 +338,7 @@ foreach (array_map('basename', glob('skins/'.$skin.'/css/*', GLOB_ONLYDIR)) as $
|
||||
echo '<p class="warning">Note: This value has been overriden via configuration files in '.ZM_CONFIG. ' or ' . ZM_CONFIG_SUBDIR.'.<br/>The overriden value is: '.constant($name).'</p>'.PHP_EOL;
|
||||
}
|
||||
?>
|
||||
<span class="form-text form-control-sm"><?php echo validHtmlStr($optionPromptText); echo makeHelpLink($name) ?></span>
|
||||
<span class="form-text"><?php echo validHtmlStr($optionPromptText); echo makeHelpLink($name) ?></span>
|
||||
</div><!-- End .col-md -->
|
||||
</div><!-- End .form-group -->
|
||||
<?php
|
||||
|
||||
Reference in New Issue
Block a user