Merge branch 'dashboard-v3' into 'data-port'
# Conflicts: # libs/ffmpeg.jsmontage-api
commit
41037abaf9
|
|
@ -100,8 +100,8 @@ RUN sed -i -e 's/\r//g' /home/Shinobi/Docker/init.sh
|
||||||
|
|
||||||
VOLUME ["/home/Shinobi/videos"]
|
VOLUME ["/home/Shinobi/videos"]
|
||||||
VOLUME ["/home/Shinobi/plugins"]
|
VOLUME ["/home/Shinobi/plugins"]
|
||||||
|
VOLUME ["/home/Shinobi/libs/customAutoLoad"]
|
||||||
VOLUME ["/config"]
|
VOLUME ["/config"]
|
||||||
VOLUME ["/customAutoLoad"]
|
|
||||||
VOLUME ["/var/lib/mysql"]
|
VOLUME ["/var/lib/mysql"]
|
||||||
|
|
||||||
EXPOSE 8080 443 21 25
|
EXPOSE 8080 443 21 25
|
||||||
|
|
|
||||||
|
|
@ -98,8 +98,8 @@ RUN chmod -f +x /home/Shinobi/Docker/init.sh
|
||||||
|
|
||||||
VOLUME ["/home/Shinobi/videos"]
|
VOLUME ["/home/Shinobi/videos"]
|
||||||
VOLUME ["/home/Shinobi/plugins"]
|
VOLUME ["/home/Shinobi/plugins"]
|
||||||
|
VOLUME ["/home/Shinobi/libs/customAutoLoad"]
|
||||||
VOLUME ["/config"]
|
VOLUME ["/config"]
|
||||||
VOLUME ["/customAutoLoad"]
|
|
||||||
VOLUME ["/var/lib/mysql"]
|
VOLUME ["/var/lib/mysql"]
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,8 @@ RUN sed -i -e 's/\r//g' /home/Shinobi/Docker/init.sh
|
||||||
|
|
||||||
VOLUME ["/home/Shinobi/videos"]
|
VOLUME ["/home/Shinobi/videos"]
|
||||||
VOLUME ["/home/Shinobi/plugins"]
|
VOLUME ["/home/Shinobi/plugins"]
|
||||||
|
VOLUME ["/home/Shinobi/libs/customAutoLoad"]
|
||||||
VOLUME ["/config"]
|
VOLUME ["/config"]
|
||||||
VOLUME ["/customAutoLoad"]
|
|
||||||
VOLUME ["/var/lib/mysql"]
|
VOLUME ["/var/lib/mysql"]
|
||||||
|
|
||||||
EXPOSE 8080 443 21 25
|
EXPOSE 8080 443 21 25
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ if [ "${ffmpeginstall^}" = "Y" ]; then
|
||||||
elif [ "$version" = 8 ]; then
|
elif [ "$version" = 8 ]; then
|
||||||
#Enable Negativo17 repo for FFMPEG (CentOS 8)
|
#Enable Negativo17 repo for FFMPEG (CentOS 8)
|
||||||
sudo dnf install epel-release dnf-utils -y -q -e 0
|
sudo dnf install epel-release dnf-utils -y -q -e 0
|
||||||
sudo yum-config-manager --set-enabled PowerTools
|
sudo yum-config-manager --set-enabled powertools
|
||||||
sudo yum-config-manager --add-repo=https://negativo17.org/repos/epel-multimedia.repo
|
sudo yum-config-manager --add-repo=https://negativo17.org/repos/epel-multimedia.repo
|
||||||
sudo dnf install ffmpeg ffmpeg-devel -y -q -e 0
|
sudo dnf install ffmpeg ffmpeg-devel -y -q -e 0
|
||||||
fi
|
fi
|
||||||
|
|
@ -148,7 +148,7 @@ if [ "${installdbserver^}" = "Y" ] || [ "${installdbserver^}" = "" ]; then
|
||||||
|
|
||||||
if [ "${securedbserver^}" = "Y" ]; then
|
if [ "${securedbserver^}" = "Y" ]; then
|
||||||
#Configure basic security for MariaDB
|
#Configure basic security for MariaDB
|
||||||
sudo mysql_secure_installation
|
sudo mariadb-secure-installation
|
||||||
else
|
else
|
||||||
echo "========================================================="
|
echo "========================================================="
|
||||||
echo "Skipping database server security configuration..."
|
echo "Skipping database server security configuration..."
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ if [ "$mysqlagree" = "y" ] || [ "$mysqlagree" = "Y" ]; then
|
||||||
sudo systemctl start mariadb
|
sudo systemctl start mariadb
|
||||||
sudo systemctl enable mariadb
|
sudo systemctl enable mariadb
|
||||||
#Run mysql install
|
#Run mysql install
|
||||||
sudo mysql_secure_installation
|
sudo mariadb-secure-installation
|
||||||
fi
|
fi
|
||||||
echo "============="
|
echo "============="
|
||||||
echo "Shinobi - Database Installation"
|
echo "Shinobi - Database Installation"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/bin/sh
|
||||||
|
echo "------------------------------------------"
|
||||||
|
echo "-- Installing CUDA Toolkit and CUDA DNN --"
|
||||||
|
echo "------------------------------------------"
|
||||||
|
# Install CUDA Drivers and Toolkit
|
||||||
|
if [ -x "$(command -v apt)" ]; then
|
||||||
|
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
|
||||||
|
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
|
||||||
|
wget http://developer.download.nvidia.com/compute/cuda/11.0.2/local_installers/cuda-repo-ubuntu2004-11-0-local_11.0.2-450.51.05-1_amd64.deb
|
||||||
|
sudo dpkg -i cuda-repo-ubuntu2004-11-0-local_11.0.2-450.51.05-1_amd64.deb
|
||||||
|
sudo apt-key add /var/cuda-repo-ubuntu2004-11-0-local/7fa2af80.pub
|
||||||
|
|
||||||
|
sudo apt-get update -y
|
||||||
|
|
||||||
|
sudo apt-get -o Dpkg::Options::="--force-overwrite" install cuda-toolkit-11-0 -y --no-install-recommends
|
||||||
|
sudo apt-get -o Dpkg::Options::="--force-overwrite" install --fix-broken -y
|
||||||
|
sudo apt install nvidia-utils-495 nvidia-headless-495 -y
|
||||||
|
|
||||||
|
# Install CUDA DNN
|
||||||
|
wget https://cdn.shinobi.video/installers/libcudnn8_8.2.1.32-1+cuda11.3_amd64.deb -O cuda-dnn.deb
|
||||||
|
sudo dpkg -i cuda-dnn.deb
|
||||||
|
wget https://cdn.shinobi.video/installers/libcudnn8-dev_8.2.0.53-1+cuda11.3_amd64.deb -O cuda-dnn-dev.deb
|
||||||
|
sudo dpkg -i cuda-dnn-dev.deb
|
||||||
|
echo "-- Cleaning Up --"
|
||||||
|
# Cleanup
|
||||||
|
sudo rm cuda-dnn.deb
|
||||||
|
sudo rm cuda-dnn-dev.deb
|
||||||
|
fi
|
||||||
|
echo "------------------------------"
|
||||||
|
echo "Reboot is required. Do it now?"
|
||||||
|
echo "------------------------------"
|
||||||
|
echo "(y)es or (N)o. Default is No."
|
||||||
|
read rebootTheMachineHomie
|
||||||
|
if [ "$rebootTheMachineHomie" = "y" ] || [ "$rebootTheMachineHomie" = "Y" ]; then
|
||||||
|
sudo reboot
|
||||||
|
fi
|
||||||
9
cron.js
9
cron.js
|
|
@ -92,8 +92,8 @@ const deleteVideo = (x) => {
|
||||||
const deleteFileBinEntry = (x) => {
|
const deleteFileBinEntry = (x) => {
|
||||||
postMessage({f:'s.deleteFileBinEntry',file:x})
|
postMessage({f:'s.deleteFileBinEntry',file:x})
|
||||||
}
|
}
|
||||||
const setDiskUsedForGroup = (groupKey,size,target) => {
|
const setDiskUsedForGroup = (groupKey,size,target,videoRow) => {
|
||||||
postMessage({f:'s.setDiskUsedForGroup', ke: groupKey, size: size, target: target})
|
postMessage({f:'s.setDiskUsedForGroup', ke: groupKey, size: size, target: target, videoRow: videoRow})
|
||||||
}
|
}
|
||||||
const getVideoDirectory = function(e){
|
const getVideoDirectory = function(e){
|
||||||
if(e.mid&&!e.id){e.id=e.mid};
|
if(e.mid&&!e.id){e.id=e.mid};
|
||||||
|
|
@ -185,6 +185,7 @@ const checkFilterRules = function(v){
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const deleteVideosByDays = async (v,days,addedQueries) => {
|
const deleteVideosByDays = async (v,days,addedQueries) => {
|
||||||
|
const groupKey = v.ke;
|
||||||
const whereQuery = [
|
const whereQuery = [
|
||||||
['ke','=',v.ke],
|
['ke','=',v.ke],
|
||||||
['time','<', sqlDate(days+' DAY')],
|
['time','<', sqlDate(days+' DAY')],
|
||||||
|
|
@ -207,7 +208,8 @@ const deleteVideosByDays = async (v,days,addedQueries) => {
|
||||||
const filename = formattedTime(row.time) + '.' + row.ext
|
const filename = formattedTime(row.time) + '.' + row.ext
|
||||||
try{
|
try{
|
||||||
await fs.promises.unlink(dir + filename)
|
await fs.promises.unlink(dir + filename)
|
||||||
row.size += clearSize
|
const fileSizeMB = row.size / 1048576;
|
||||||
|
setDiskUsedForGroup(groupKey,-fileSizeMB,null,row)
|
||||||
sendToWebSocket({
|
sendToWebSocket({
|
||||||
f: 'video_delete',
|
f: 'video_delete',
|
||||||
filename: filename + '.' + row.ext,
|
filename: filename + '.' + row.ext,
|
||||||
|
|
@ -227,7 +229,6 @@ const deleteVideosByDays = async (v,days,addedQueries) => {
|
||||||
where: whereQuery
|
where: whereQuery
|
||||||
})
|
})
|
||||||
affectedRows = deleteResponse.rows || 0
|
affectedRows = deleteResponse.rows || 0
|
||||||
setDiskUsedForGroup(v.ke,-clearSize)
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,7 @@ module.exports = function(s,config,lang){
|
||||||
{
|
{
|
||||||
"fieldType": 'div',
|
"fieldType": 'div',
|
||||||
"id": "monitorPresetsSelection",
|
"id": "monitorPresetsSelection",
|
||||||
|
"style": "max-height:400px;overflow:auto;",
|
||||||
"class": "mdl-list"
|
"class": "mdl-list"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -497,23 +498,23 @@ module.exports = function(s,config,lang){
|
||||||
"form-group-class": "input-mapping",
|
"form-group-class": "input-mapping",
|
||||||
"possible": [
|
"possible": [
|
||||||
{
|
{
|
||||||
"name": lang['All streams in first feed'] + '(0, ' + lang.Default + ')',
|
"name": lang['All streams in first feed'] + ' (0, ' + lang.Default + ')',
|
||||||
"value": "0"
|
"value": "0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": lang['First stream in feed'] + '(0:0)',
|
"name": lang['First stream in feed'] + ' (0:0)',
|
||||||
"value": "0:0"
|
"value": "0:0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": lang['Second stream in feed'] + "(0:1)",
|
"name": lang['Second stream in feed'] + " (0:1)",
|
||||||
"value": "0:1"
|
"value": "0:1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": lang['Video streams only'] + "(0:v)",
|
"name": lang['Video streams only'] + " (0:v)",
|
||||||
"value": "0:v"
|
"value": "0:v"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": lang['Video stream only from first feed'] + "(0:v:0)",
|
"name": lang['Video stream only from first feed'] + " (0:v:0)",
|
||||||
"value": "0:v:0"
|
"value": "0:v:0"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
@ -764,6 +765,10 @@ module.exports = function(s,config,lang){
|
||||||
"name": lang['HLS (includes Audio)'],
|
"name": lang['HLS (includes Audio)'],
|
||||||
"value": "hls",
|
"value": "hls",
|
||||||
"info": "Similar method to facebook live streams. <b>Includes audio</b> if input provides it. There is a delay of about 4-6 seconds because this method records segments then pushes them to the client rather than push as while it creates them."
|
"info": "Similar method to facebook live streams. <b>Includes audio</b> if input provides it. There is a delay of about 4-6 seconds because this method records segments then pushes them to the client rather than push as while it creates them."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": lang.useSubStreamOnlyWhenWatching,
|
||||||
|
"value": "useSubstream",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -1285,16 +1290,20 @@ module.exports = function(s,config,lang){
|
||||||
isAdvanced: true,
|
isAdvanced: true,
|
||||||
"isSection": true,
|
"isSection": true,
|
||||||
"id": "monSectionSubstream",
|
"id": "monSectionSubstream",
|
||||||
|
"blockquote": lang.substreamText,
|
||||||
|
"blockquoteClass": 'global_tip',
|
||||||
"info": [
|
"info": [
|
||||||
{
|
{
|
||||||
"name": lang['Connection'],
|
"name": lang['Connection'],
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
id: "monSectionSubstreamInput",
|
id: "monSectionSubstreamInput",
|
||||||
|
"blockquote": lang.substreamConnectionText,
|
||||||
|
"blockquoteClass": 'global_tip',
|
||||||
isSection: true,
|
isSection: true,
|
||||||
isFormGroupGroup: true,
|
isFormGroupGroup: true,
|
||||||
"info": [
|
"info": [
|
||||||
{
|
{
|
||||||
name:'map-detail=type',
|
name:'detail-substream-input=type',
|
||||||
field:lang['Input Type'],
|
field:lang['Input Type'],
|
||||||
default:'h264',
|
default:'h264',
|
||||||
attribute:'selector="h_i_SUBSTREAM_FIELDS"',
|
attribute:'selector="h_i_SUBSTREAM_FIELDS"',
|
||||||
|
|
@ -1303,7 +1312,8 @@ module.exports = function(s,config,lang){
|
||||||
possible:[
|
possible:[
|
||||||
{
|
{
|
||||||
"name": "H.264 / H.265 / H.265+",
|
"name": "H.264 / H.265 / H.265+",
|
||||||
"value": "h264"
|
"value": "h264",
|
||||||
|
selected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "JPEG",
|
"name": "JPEG",
|
||||||
|
|
@ -1371,6 +1381,7 @@ module.exports = function(s,config,lang){
|
||||||
{
|
{
|
||||||
"name": lang.Yes,
|
"name": lang.Yes,
|
||||||
"value": "1",
|
"value": "1",
|
||||||
|
selected: true,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -1385,7 +1396,8 @@ module.exports = function(s,config,lang){
|
||||||
{
|
{
|
||||||
"name": lang.Auto,
|
"name": lang.Auto,
|
||||||
"value": "",
|
"value": "",
|
||||||
"info": "Let FFMPEG decide. Normally it will try UDP first."
|
"info": "Let FFMPEG decide. Normally it will try UDP first.",
|
||||||
|
selected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "TCP",
|
"name": "TCP",
|
||||||
|
|
@ -1410,6 +1422,7 @@ module.exports = function(s,config,lang){
|
||||||
{
|
{
|
||||||
"name": lang.No,
|
"name": lang.No,
|
||||||
"value": "0",
|
"value": "0",
|
||||||
|
selected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": lang.Yes,
|
"name": lang.Yes,
|
||||||
|
|
@ -1438,7 +1451,8 @@ module.exports = function(s,config,lang){
|
||||||
possible:[
|
possible:[
|
||||||
{
|
{
|
||||||
"name": lang.Auto + '('+lang.Recommended+')',
|
"name": lang.Auto + '('+lang.Recommended+')',
|
||||||
"value": ""
|
"value": "",
|
||||||
|
selected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": lang.NVIDIA,
|
"name": lang.NVIDIA,
|
||||||
|
|
@ -1516,6 +1530,8 @@ module.exports = function(s,config,lang){
|
||||||
"name": lang['Output'],
|
"name": lang['Output'],
|
||||||
"color": "blue",
|
"color": "blue",
|
||||||
id: "monSectionSubstreamOutput",
|
id: "monSectionSubstreamOutput",
|
||||||
|
"blockquote": lang.substreamOutputText,
|
||||||
|
"blockquoteClass": 'global_tip',
|
||||||
isSection: true,
|
isSection: true,
|
||||||
isFormGroupGroup: true,
|
isFormGroupGroup: true,
|
||||||
"info": [
|
"info": [
|
||||||
|
|
@ -1531,41 +1547,22 @@ module.exports = function(s,config,lang){
|
||||||
{
|
{
|
||||||
"name": lang.Poseidon,
|
"name": lang.Poseidon,
|
||||||
"value": "mp4",
|
"value": "mp4",
|
||||||
"info": "Poseidon is built on Kevin Godell's MP4 processing code. It simulates a streaming MP4 file but using the data of a live stream. Includes Audio. Some browsers can play it like a regular MP4 file. Streams over HTTP or WebSocket."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": lang["RTMP Stream"],
|
|
||||||
"value": "rtmp",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": lang['MJPEG'],
|
"name": lang['MJPEG'],
|
||||||
"value": "mjpeg",
|
"value": "mjpeg",
|
||||||
"info": "Standard Motion JPEG image. No audio."
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": lang['FLV'],
|
"name": lang['FLV'],
|
||||||
"value": "flv",
|
"value": "flv",
|
||||||
"info": "Sending FLV encoded frames over WebSocket."
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": lang['HLS (includes Audio)'],
|
"name": lang['HLS (includes Audio)'],
|
||||||
"value": "hls",
|
"value": "hls",
|
||||||
"info": "Similar method to facebook live streams. <b>Includes audio</b> if input provides it. There is a delay of about 4-6 seconds because this method records segments then pushes them to the client rather than push as while it creates them."
|
selected: true,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"field": lang['Server URL'],
|
|
||||||
"name": `detail-substream-output="rtmp_server_url"`,
|
|
||||||
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_rtmp",
|
|
||||||
"example": "rtmp://live-api.facebook.com:80/rtmp/",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field": lang['Stream Key'],
|
|
||||||
"name": `detail-substream-output="rtmp_stream_key"`,
|
|
||||||
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_rtmp",
|
|
||||||
"example": "1111111111?ds=1&a=xxxxxxxxxx",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"field": lang['# of Allow MJPEG Clients'],
|
"field": lang['# of Allow MJPEG Clients'],
|
||||||
"name": `detail-substream-output="stream_mjpeg_clients"`,
|
"name": `detail-substream-output="stream_mjpeg_clients"`,
|
||||||
|
|
@ -1577,14 +1574,15 @@ module.exports = function(s,config,lang){
|
||||||
"name": `detail-substream-output="stream_vcodec"`,
|
"name": `detail-substream-output="stream_vcodec"`,
|
||||||
"description": "Video codec for streaming.",
|
"description": "Video codec for streaming.",
|
||||||
"default": "copy",
|
"default": "copy",
|
||||||
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
||||||
"fieldType": "select",
|
"fieldType": "select",
|
||||||
"selector": "h_hls_v_channel_SUBSTREAM_FIELDS",
|
"selector": "h_hls_v_channel_SUBSTREAM_FIELDS",
|
||||||
"possible": [
|
"possible": [
|
||||||
{
|
{
|
||||||
"name": lang.Auto,
|
"name": lang.Auto,
|
||||||
"value": "no",
|
"value": "no",
|
||||||
"info": "Let FFMPEG choose."
|
"info": "Let FFMPEG choose.",
|
||||||
|
selected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "libx264",
|
"name": "libx264",
|
||||||
|
|
@ -1659,12 +1657,13 @@ module.exports = function(s,config,lang){
|
||||||
"default": "",
|
"default": "",
|
||||||
"example": "",
|
"example": "",
|
||||||
"fieldType": "select",
|
"fieldType": "select",
|
||||||
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
||||||
"possible": [
|
"possible": [
|
||||||
{
|
{
|
||||||
"name": lang.Auto,
|
"name": lang.Auto,
|
||||||
"info": "Let FFMPEG choose.",
|
"info": "Let FFMPEG choose.",
|
||||||
"value": ""
|
"value": "",
|
||||||
|
selected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": lang["No Audio"],
|
"name": lang["No Audio"],
|
||||||
|
|
@ -1730,7 +1729,7 @@ module.exports = function(s,config,lang){
|
||||||
"description": "Low number means higher quality. Higher number means less quality.",
|
"description": "Low number means higher quality. Higher number means less quality.",
|
||||||
"default": "15",
|
"default": "15",
|
||||||
"example": "1",
|
"example": "1",
|
||||||
"form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
// "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
||||||
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
||||||
"possible": "1-23"
|
"possible": "1-23"
|
||||||
},
|
},
|
||||||
|
|
@ -1752,7 +1751,7 @@ module.exports = function(s,config,lang){
|
||||||
"name": "detail-substream-output=stream_fps",
|
"name": "detail-substream-output=stream_fps",
|
||||||
"field": lang['Frame Rate'],
|
"field": lang['Frame Rate'],
|
||||||
"description": "The speed in which frames are displayed to clients, in Frames Per Second. Be aware there is no default. This can lead to high bandwidth usage.",
|
"description": "The speed in which frames are displayed to clients, in Frames Per Second. Be aware there is no default. This can lead to high bandwidth usage.",
|
||||||
"form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
// "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
||||||
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1762,7 +1761,7 @@ module.exports = function(s,config,lang){
|
||||||
"fieldType": "number",
|
"fieldType": "number",
|
||||||
"numberMin": "1",
|
"numberMin": "1",
|
||||||
"example": "640",
|
"example": "640",
|
||||||
"form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
// "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
||||||
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1772,7 +1771,7 @@ module.exports = function(s,config,lang){
|
||||||
"fieldType": "number",
|
"fieldType": "number",
|
||||||
"numberMin": "1",
|
"numberMin": "1",
|
||||||
"example": "480",
|
"example": "480",
|
||||||
"form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
// "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
||||||
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -1780,7 +1779,7 @@ module.exports = function(s,config,lang){
|
||||||
"field": lang["Rotate"],
|
"field": lang["Rotate"],
|
||||||
"description": "Change the viewing angle of the video stream.",
|
"description": "Change the viewing angle of the video stream.",
|
||||||
"fieldType": "select",
|
"fieldType": "select",
|
||||||
"form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
// "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
||||||
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
||||||
"possible": [
|
"possible": [
|
||||||
{
|
{
|
||||||
|
|
@ -1813,7 +1812,7 @@ module.exports = function(s,config,lang){
|
||||||
"name": "detail-substream-output=svf",
|
"name": "detail-substream-output=svf",
|
||||||
"field": lang["Video Filter"],
|
"field": lang["Video Filter"],
|
||||||
"description": "Place FFMPEG video filters in this box to affect the streaming portion. No spaces.",
|
"description": "Place FFMPEG video filters in this box to affect the streaming portion. No spaces.",
|
||||||
"form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
// "form-group-class-pre-layer": "h_hls_v_channel_SUBSTREAM_FIELDS_input h_hls_v_channel_SUBSTREAM_FIELDS_libx264 h_hls_v_channel_SUBSTREAM_FIELDS_libx265 h_hls_v_channel_SUBSTREAM_FIELDS_h264_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_hevc_nvenc h_hls_v_channel_SUBSTREAM_FIELDS_no",
|
||||||
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
"form-group-class": "h_st_channel_SUBSTREAM_FIELDS_input h_st_channel_SUBSTREAM_FIELDS_mjpeg h_st_channel_SUBSTREAM_FIELDS_hls h_st_channel_SUBSTREAM_FIELDS_rtmp h_st_channel_SUBSTREAM_FIELDS_jsmpeg h_st_channel_SUBSTREAM_FIELDS_flv h_st_channel_SUBSTREAM_FIELDS_mp4 h_st_channel_SUBSTREAM_FIELDS_h264",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -2425,6 +2424,10 @@ module.exports = function(s,config,lang){
|
||||||
"name": `.5 ${lang.minutes}`,
|
"name": `.5 ${lang.minutes}`,
|
||||||
"value": "30"
|
"value": "30"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": `1 ${lang.minute}`,
|
||||||
|
"value": "60"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": `5 ${lang.minutes}`,
|
"name": `5 ${lang.minutes}`,
|
||||||
"value": "300"
|
"value": "300"
|
||||||
|
|
@ -5103,11 +5106,22 @@ module.exports = function(s,config,lang){
|
||||||
"blockquoteClass": "global_tip",
|
"blockquoteClass": "global_tip",
|
||||||
"blockquote": lang.onvifdeviceManagerGlobalTip,
|
"blockquote": lang.onvifdeviceManagerGlobalTip,
|
||||||
"info": [
|
"info": [
|
||||||
|
{
|
||||||
|
"field": lang["Monitor"],
|
||||||
|
"fieldType": "select",
|
||||||
|
"class": "monitors_list",
|
||||||
|
"possible": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldType": "btn",
|
"fieldType": "btn",
|
||||||
"class": `btn-warning onvif-device-reboot`,
|
"class": `btn-warning onvif-device-reboot`,
|
||||||
"btnContent": `<i class="fa fa-refresh"></i> ${lang['Reboot Camera']}`,
|
"btnContent": `<i class="fa fa-refresh"></i> ${lang['Reboot Camera']}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldType": "div",
|
||||||
|
"class": "p-2",
|
||||||
|
"divContent": `<pre class="bg-dark text-white" style="max-height: 400px;overflow: auto;" id="onvifDeviceManagerInfo"></pre>`,
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Network": {
|
"Network": {
|
||||||
|
|
@ -5987,6 +6001,21 @@ module.exports = function(s,config,lang){
|
||||||
"Schedules": {
|
"Schedules": {
|
||||||
"section": "Schedules",
|
"section": "Schedules",
|
||||||
"blocks": {
|
"blocks": {
|
||||||
|
"Info": {
|
||||||
|
"name": lang["Monitor States and Schedules"],
|
||||||
|
"color": "blue",
|
||||||
|
"section-pre-class": "col-md-12",
|
||||||
|
"blockquoteClass": "global_tip",
|
||||||
|
"blockquote": lang.MonitorStatesText,
|
||||||
|
"info": [
|
||||||
|
{
|
||||||
|
"fieldType": "btn",
|
||||||
|
"attribute": `page-open="monitorStates"`,
|
||||||
|
"class": `btn-primary`,
|
||||||
|
"btnContent": `<i class="fa fa-align-right"></i> ${lang["Monitor States"]}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
"Schedules": {
|
"Schedules": {
|
||||||
"name": lang["Schedules"],
|
"name": lang["Schedules"],
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
|
|
@ -6118,8 +6147,23 @@ module.exports = function(s,config,lang){
|
||||||
"Monitor States": {
|
"Monitor States": {
|
||||||
"section": "Monitor States",
|
"section": "Monitor States",
|
||||||
"blocks": {
|
"blocks": {
|
||||||
|
"Info": {
|
||||||
|
"name": lang["Monitor States and Schedules"],
|
||||||
|
"color": "blue",
|
||||||
|
"section-pre-class": "col-md-12",
|
||||||
|
"blockquoteClass": "global_tip",
|
||||||
|
"blockquote": lang.MonitorStatesText,
|
||||||
|
"info": [
|
||||||
|
{
|
||||||
|
"fieldType": "btn",
|
||||||
|
"attribute": `page-open="schedules"`,
|
||||||
|
"class": `btn-primary`,
|
||||||
|
"btnContent": `<i class="fa fa-clock"></i> ${lang["Schedules"]}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
"Monitor States": {
|
"Monitor States": {
|
||||||
"name": lang["Monitor States"],
|
noHeader: true,
|
||||||
"color": "green",
|
"color": "green",
|
||||||
"section-pre-class": "col-md-6",
|
"section-pre-class": "col-md-6",
|
||||||
"info": [
|
"info": [
|
||||||
|
|
@ -6758,7 +6802,7 @@ module.exports = function(s,config,lang){
|
||||||
"section": "Monitor Settings Additional Input Map",
|
"section": "Monitor Settings Additional Input Map",
|
||||||
"blocks": {
|
"blocks": {
|
||||||
"Connection" : {
|
"Connection" : {
|
||||||
"id": `monSectionMap$[NUMBER]`,
|
"id": `monSectionMap$[TEMP_ID]`,
|
||||||
"name": `${lang['Input Map']} $[NUMBER]`,
|
"name": `${lang['Input Map']} $[NUMBER]`,
|
||||||
"section-class": "input-map",
|
"section-class": "input-map",
|
||||||
"color": "orange",
|
"color": "orange",
|
||||||
|
|
@ -7350,8 +7394,11 @@ module.exports = function(s,config,lang){
|
||||||
<div class="monitor_details">
|
<div class="monitor_details">
|
||||||
<div class="pull-left">
|
<div class="pull-left">
|
||||||
<a title="${lang['Options']}" class="btn btn-sm badge btn-secondary toggle-live-grid-monitor-menu"><i class="fa fa-bars"></i></a>
|
<a title="${lang['Options']}" class="btn btn-sm badge btn-secondary toggle-live-grid-monitor-menu"><i class="fa fa-bars"></i></a>
|
||||||
|
<a title="${lang['Edit']}" class="btn btn-sm badge btn-primary open-monitor-settings"><i class="fa fa-wrench"></i></a>
|
||||||
|
<a title="${lang['Toggle Substream']}" class="btn btn-sm badge btn-secondary toggle-monitor-substream"><i class="fa fa-eye"></i></a>
|
||||||
<a title="${lang['Snapshot']}" class="btn btn-sm badge btn-warning snapshot-live-grid-monitor"><i class="fa fa-camera"></i></a>
|
<a title="${lang['Snapshot']}" class="btn btn-sm badge btn-warning snapshot-live-grid-monitor"><i class="fa fa-camera"></i></a>
|
||||||
<a title="${lang['Videos List']}" class="btn btn-sm badge btn-secondary open-videos"><i class="fa fa-film"></i></a>
|
<a title="${lang['Videos List']}" class="btn btn-sm badge btn-secondary open-videos"><i class="fa fa-film"></i></a>
|
||||||
|
<a title="${lang['Show Logs']}" class="btn btn-sm badge btn-warning toggle-live-grid-monitor-logs"><i class="fa fa-exclamation-triangle"></i></a>
|
||||||
<a title="${lang['Close']}" class="btn btn-sm badge btn-danger close-live-grid-monitor"><i class="fa fa-times"></i></a>
|
<a title="${lang['Close']}" class="btn btn-sm badge btn-danger close-live-grid-monitor"><i class="fa fa-times"></i></a>
|
||||||
</div>
|
</div>
|
||||||
<div><span class="monitor_name">$MONITOR_NAME</span></div>
|
<div><span class="monitor_name">$MONITOR_NAME</span></div>
|
||||||
|
|
@ -7380,6 +7427,11 @@ module.exports = function(s,config,lang){
|
||||||
"class": "warning toggle-live-grid-monitor-logs",
|
"class": "warning toggle-live-grid-monitor-logs",
|
||||||
"icon": "exclamation-triangle"
|
"icon": "exclamation-triangle"
|
||||||
},
|
},
|
||||||
|
"Show Logs": {
|
||||||
|
"label": lang['Toggle Substream'],
|
||||||
|
"class": "warning toggle-monitor-substream",
|
||||||
|
"icon": "eye"
|
||||||
|
},
|
||||||
"Control": {
|
"Control": {
|
||||||
"label": lang['Control'],
|
"label": lang['Control'],
|
||||||
"class": "default toggle-live-grid-monitor-ptz-controls",
|
"class": "default toggle-live-grid-monitor-ptz-controls",
|
||||||
|
|
@ -7523,12 +7575,48 @@ module.exports = function(s,config,lang){
|
||||||
label: `${lang['Calendar']}`,
|
label: `${lang['Calendar']}`,
|
||||||
pageOpen: 'calendarView',
|
pageOpen: 'calendarView',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'fast-forward',
|
||||||
|
label: `${lang['Time-lapse']}`,
|
||||||
|
pageOpen: 'timelapseViewer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
divider: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: 'wrench',
|
icon: 'wrench',
|
||||||
label: `${lang['Monitor Settings']}`,
|
label: `${lang['Monitor Settings']}`,
|
||||||
pageOpen: 'monitorSettings',
|
pageOpen: 'monitorSettings',
|
||||||
addUl: true,
|
addUl: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'grav',
|
||||||
|
label: `${lang['Region Editor']}`,
|
||||||
|
pageOpen: 'regionEditor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'filter',
|
||||||
|
label: `${lang['Event Filters']}`,
|
||||||
|
pageOpen: 'eventFilters',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'align-right',
|
||||||
|
label: `${lang['Monitor States']}`,
|
||||||
|
pageOpen: 'monitorStates',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'clock',
|
||||||
|
label: `${lang['Schedules']}`,
|
||||||
|
pageOpen: 'schedules',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'exclamation-triangle',
|
||||||
|
label: `${lang['Logs']}`,
|
||||||
|
pageOpen: 'logViewer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
divider: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: 'gears',
|
icon: 'gears',
|
||||||
label: `${lang['Account Settings']}`,
|
label: `${lang['Account Settings']}`,
|
||||||
|
|
@ -7541,43 +7629,13 @@ module.exports = function(s,config,lang){
|
||||||
pageOpen: 'subAccountManager',
|
pageOpen: 'subAccountManager',
|
||||||
addUl: true,
|
addUl: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: 'compass',
|
|
||||||
label: `${lang['ShinobiHub']}`,
|
|
||||||
pageOpen: 'configFinder',
|
|
||||||
addUl: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'grav',
|
|
||||||
label: `${lang['Region Editor']}`,
|
|
||||||
pageOpen: 'regionEditor',
|
|
||||||
addUl:true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: 'key',
|
icon: 'key',
|
||||||
label: `${lang['API Keys']}`,
|
label: `${lang['API Keys']}`,
|
||||||
pageOpen: 'apiKeys',
|
pageOpen: 'apiKeys',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'align-right',
|
divider: true,
|
||||||
label: `${lang['Monitor States']}`,
|
|
||||||
pageOpen: 'monitorStates',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'clock',
|
|
||||||
label: `${lang['Schedules']}`,
|
|
||||||
pageOpen: 'schedules',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'fast-forward',
|
|
||||||
label: `${lang['Time-lapse']}`,
|
|
||||||
pageOpen: 'timelapseViewer',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'filter',
|
|
||||||
label: `${lang['Event Filters']}`,
|
|
||||||
pageOpen: 'eventFilters',
|
|
||||||
addUl:true
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'search',
|
icon: 'search',
|
||||||
|
|
@ -7585,15 +7643,29 @@ module.exports = function(s,config,lang){
|
||||||
pageOpen: 'onvifScanner',
|
pageOpen: 'onvifScanner',
|
||||||
addUl:true
|
addUl:true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'opera',
|
||||||
|
label: `${lang['ONVIF Device Manager']}`,
|
||||||
|
pageOpen: 'onvifDeviceManager',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: 'eyedropper',
|
icon: 'eyedropper',
|
||||||
label: `${lang['FFprobe']}`,
|
label: `${lang['FFprobe']}`,
|
||||||
pageOpen: 'cameraProbe',
|
pageOpen: 'cameraProbe',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'exclamation-triangle',
|
icon: 'compass',
|
||||||
label: `${lang['Logs']}`,
|
label: `${lang['ShinobiHub']}`,
|
||||||
pageOpen: 'logViewer',
|
pageOpen: 'configFinder',
|
||||||
|
addUl: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
divider: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'info-circle',
|
||||||
|
label: `${lang['Help']}`,
|
||||||
|
pageOpen: 'helpWindow',
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// icon: 'exclamation-circle',
|
// icon: 'exclamation-circle',
|
||||||
|
|
@ -7710,61 +7782,6 @@ module.exports = function(s,config,lang){
|
||||||
"Power Viewer": {
|
"Power Viewer": {
|
||||||
"section": lang["Power Viewer"],
|
"section": lang["Power Viewer"],
|
||||||
"blocks": {
|
"blocks": {
|
||||||
"Search Settings": {
|
|
||||||
id: "powerVideoTabs",
|
|
||||||
"color": "blue",
|
|
||||||
noHeader: true,
|
|
||||||
noDefaultSectionClasses: true,
|
|
||||||
attribute: `tab-chooser-parent`,
|
|
||||||
"section-pre-class": "col-md-4",
|
|
||||||
"info": [
|
|
||||||
{
|
|
||||||
"color": "blue",
|
|
||||||
noHeader: true,
|
|
||||||
isSection: true,
|
|
||||||
isFormGroupGroup: true,
|
|
||||||
"info": [
|
|
||||||
{
|
|
||||||
"field": lang['Monitors'],
|
|
||||||
"id": "powerVideoMonitorsList",
|
|
||||||
"form-group-attribute": 'tab-section=monitors',
|
|
||||||
"attribute": "multiple",
|
|
||||||
"fieldType": "select",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "powerVideoDateRange",
|
|
||||||
"field": lang['Date Range'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "powerVideoVideoLimit",
|
|
||||||
"field": lang['Video Limit'] + ` (${lang['Per Monitor']})`,
|
|
||||||
"placeholder": "0",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "powerVideoEventLimit",
|
|
||||||
"field": lang['Event Limit'] + ` (${lang['Per Monitor']})`,
|
|
||||||
"placeholder": "500",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id:'powerVideoSet',
|
|
||||||
field: lang['Video Set'],
|
|
||||||
default:'h264',
|
|
||||||
"fieldType": "select",
|
|
||||||
possible:[
|
|
||||||
{
|
|
||||||
"name": lang.Local,
|
|
||||||
"value": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": lang.Cloud,
|
|
||||||
"value": "cloud"
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Video Playback": {
|
"Video Playback": {
|
||||||
id: "powerVideoVideoPlayback",
|
id: "powerVideoVideoPlayback",
|
||||||
noHeader: true,
|
noHeader: true,
|
||||||
|
|
@ -7856,13 +7873,60 @@ module.exports = function(s,config,lang){
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "powerVideoTabs",
|
||||||
|
attribute: `tab-chooser-parent`,
|
||||||
|
"color": "blue",
|
||||||
|
noHeader: true,
|
||||||
|
isSection: true,
|
||||||
|
isFormGroupGroup: true,
|
||||||
|
"info": [
|
||||||
|
{
|
||||||
|
"field": lang['Monitors'],
|
||||||
|
"id": "powerVideoMonitorsList",
|
||||||
|
"form-group-attribute": 'tab-section=monitors',
|
||||||
|
"attribute": "multiple",
|
||||||
|
"fieldType": "select",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "powerVideoDateRange",
|
||||||
|
"field": lang['Date Range'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "powerVideoVideoLimit",
|
||||||
|
"field": lang['Video Limit'] + ` (${lang['Per Monitor']})`,
|
||||||
|
"placeholder": "0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "powerVideoEventLimit",
|
||||||
|
"field": lang['Event Limit'] + ` (${lang['Per Monitor']})`,
|
||||||
|
"placeholder": "500",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id:'powerVideoSet',
|
||||||
|
field: lang['Video Set'],
|
||||||
|
default:'h264',
|
||||||
|
"fieldType": "select",
|
||||||
|
possible:[
|
||||||
|
{
|
||||||
|
"name": lang.Local,
|
||||||
|
"value": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": lang.Cloud,
|
||||||
|
"value": "cloud"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Time Strip": {
|
"Time Strip": {
|
||||||
id: "powerVideoTimelineStripsContainer",
|
id: "powerVideoTimelineStripsContainer",
|
||||||
noHeader: true,
|
noHeader: true,
|
||||||
"color": "bg-gradient-blue text-white",
|
"color": "bg-gradient-blue text-white",
|
||||||
"section-pre-class": "col-md-12 mt-3",
|
"section-pre-class": "col-md-4",
|
||||||
"info": [
|
"info": [
|
||||||
{
|
{
|
||||||
"id": "powerVideoTimelineStrips",
|
"id": "powerVideoTimelineStrips",
|
||||||
|
|
@ -7870,7 +7934,7 @@ module.exports = function(s,config,lang){
|
||||||
"divContent": `<div class="loading"><i class="fa fa-hand-pointer-o"></i><div class="epic-text">${lang['Select a Monitor']}</div></div>`,
|
"divContent": `<div class="loading"><i class="fa fa-hand-pointer-o"></i><div class="epic-text">${lang['Select a Monitor']}</div></div>`,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Calendar": {
|
"Calendar": {
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,14 @@
|
||||||
"Use Raw Snapshot": "Use Raw Snapshot",
|
"Use Raw Snapshot": "Use Raw Snapshot",
|
||||||
"Login": "Login",
|
"Login": "Login",
|
||||||
"Substream": "Substream",
|
"Substream": "Substream",
|
||||||
|
"Use Substream": "Use Substream",
|
||||||
|
"useSubStreamOnlyWhenWatching": "Only When Watching, Use Substream",
|
||||||
|
"substreamText": "This is an On-Demand method of viewing the Live Stream. You can make it so the viewing process is available only when someone is watching or to be used for switching between Low and High Resolution.",
|
||||||
|
"substreamConnectionText": "You can leave the Connection detail as-is if you want it to use the main Connection information set above.",
|
||||||
|
"substreamOutputText": "Here you can set the On-Demand Stream's configuration. Learn about <a href='https://hub.shinobi.video/articles/view/Eug1dxIdhwY6zTw' target='_blank'>latency of Stream types here.</a>",
|
||||||
"Toggle Substream": "Toggle Substream",
|
"Toggle Substream": "Toggle Substream",
|
||||||
"Output": "Output",
|
"Output": "Output",
|
||||||
|
"SubstreamNotConfigured": "Substream not configured. Open your Monitor Settings and configure it.",
|
||||||
"Substream Process": "Substream Process",
|
"Substream Process": "Substream Process",
|
||||||
"Welcome": "Welcome!",
|
"Welcome": "Welcome!",
|
||||||
"API Key Action Failed": "API Key Action Failed",
|
"API Key Action Failed": "API Key Action Failed",
|
||||||
|
|
@ -508,6 +514,7 @@
|
||||||
"ago": "ago",
|
"ago": "ago",
|
||||||
"a few seconds": "a few seconds",
|
"a few seconds": "a few seconds",
|
||||||
"a minute": "a minute",
|
"a minute": "a minute",
|
||||||
|
"minute": "minute",
|
||||||
"minutes": "minutes",
|
"minutes": "minutes",
|
||||||
"an hour": "an hour",
|
"an hour": "an hour",
|
||||||
"hours": "hours",
|
"hours": "hours",
|
||||||
|
|
@ -577,6 +584,7 @@
|
||||||
"Creation Interval": "Creation Interval",
|
"Creation Interval": "Creation Interval",
|
||||||
"Plugin": "Plugin",
|
"Plugin": "Plugin",
|
||||||
"Plugin Manager": "Plugin Manager",
|
"Plugin Manager": "Plugin Manager",
|
||||||
|
"MonitorStatesText": "You can learn about how to use this <a href='https://hub.shinobi.video/articles/view/6ylYHj9MemlZwrM' target='_blank'>here on ShinobiHub</a>.",
|
||||||
"IdentityText1": "This is how the system will identify the data for this stream. You cannot change the <b>Monitor ID</b> once you have pressed save. If you want you can make the <b>Monitor ID</b> more human readable before you continue.",
|
"IdentityText1": "This is how the system will identify the data for this stream. You cannot change the <b>Monitor ID</b> once you have pressed save. If you want you can make the <b>Monitor ID</b> more human readable before you continue.",
|
||||||
"IdentityText2": "You can duplicate a monitor by modifying the <b>Monitor ID</b> then pressing save. You <b>cannot</b> use the ID of a monitor that already exists or it will save over that monitor's database information.",
|
"IdentityText2": "You can duplicate a monitor by modifying the <b>Monitor ID</b> then pressing save. You <b>cannot</b> use the ID of a monitor that already exists or it will save over that monitor's database information.",
|
||||||
"opencvCascadesText": "If you see nothing here then just download this package of <a href=\"https://cdn.shinobi.video/weights/cascades.zip\">cascades</a>. Drop them into <code>plugins/opencv/cascades</code> then press refresh <i class=\"fa fa-retweet\"></i>.",
|
"opencvCascadesText": "If you see nothing here then just download this package of <a href=\"https://cdn.shinobi.video/weights/cascades.zip\">cascades</a>. Drop them into <code>plugins/opencv/cascades</code> then press refresh <i class=\"fa fa-retweet\"></i>.",
|
||||||
|
|
@ -727,6 +735,7 @@
|
||||||
"NotifyErrorText": "Sending Notification caused an Error",
|
"NotifyErrorText": "Sending Notification caused an Error",
|
||||||
"Check the Channel ID": "Check the Channel ID",
|
"Check the Channel ID": "Check the Channel ID",
|
||||||
"Check the Recipient ID": "Check the Recipient ID",
|
"Check the Recipient ID": "Check the Recipient ID",
|
||||||
|
"AppNotEnabledText": "App Not Enabled, Enable it in your Account Settings.",
|
||||||
"DiscordNotEnabledText": "Discord Bot Not Enabled, Enable it in your Account Settings.",
|
"DiscordNotEnabledText": "Discord Bot Not Enabled, Enable it in your Account Settings.",
|
||||||
"Account Settings": "Account Settings",
|
"Account Settings": "Account Settings",
|
||||||
"How to Record": "How to Record",
|
"How to Record": "How to Record",
|
||||||
|
|
@ -1247,6 +1256,7 @@
|
||||||
"Close All Monitors": "Close All Monitors",
|
"Close All Monitors": "Close All Monitors",
|
||||||
"Daily Events": "Daily Events",
|
"Daily Events": "Daily Events",
|
||||||
"Send Notification": "Send Notification",
|
"Send Notification": "Send Notification",
|
||||||
|
"Send to": "Send to",
|
||||||
"setMaxStorageAmountText": "You should set your Max Storage Amount in your Account Settings located on the left. Find the option under the Profile section. Default is 10 GB.",
|
"setMaxStorageAmountText": "You should set your Max Storage Amount in your Account Settings located on the left. Find the option under the Profile section. Default is 10 GB.",
|
||||||
"Save Events": "Save Events",
|
"Save Events": "Save Events",
|
||||||
"Original Choice": "Original Choice",
|
"Original Choice": "Original Choice",
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@
|
||||||
"Enable Nightvision": "Ativar Visão Noturna",
|
"Enable Nightvision": "Ativar Visão Noturna",
|
||||||
"Disable Nightvision": "Desativar Visão Noturna",
|
"Disable Nightvision": "Desativar Visão Noturna",
|
||||||
"Current": "Atual",
|
"Current": "Atual",
|
||||||
"Monitors": "Monitors",
|
"Monitors": "Monitores",
|
||||||
"Video": "Video",
|
"Video": "Video",
|
||||||
"Videos": "Videos",
|
"Videos": "Videos",
|
||||||
"Events": "Eventos",
|
"Events": "Eventos",
|
||||||
|
|
@ -122,12 +122,12 @@
|
||||||
"Execute Command": "Executar Comando",
|
"Execute Command": "Executar Comando",
|
||||||
"for Global Access": "para Acesso Global",
|
"for Global Access": "para Acesso Global",
|
||||||
"Help": "Ajuda",
|
"Help": "Ajuda",
|
||||||
"Don't show this anymore": "Não mostre isso mais",
|
"Don't show this anymore": "Não me mostre isso mais",
|
||||||
"Chat on Discord": "Conversar no Discord",
|
"Chat on Discord": "Conversar no Discord",
|
||||||
"Documentation": "Documentação",
|
"Documentation": "Documentação",
|
||||||
"All Monitors": "Todos Monitores",
|
"All Monitors": "Todos Monitores",
|
||||||
"Motion Meter": "Medidos de Movimento",
|
"Motion Meter": "Medição de movimentos",
|
||||||
"FFmpegTip": "FFprobe is a simple multimedia streams analyzer. You can use it to output all kinds of information about an input including duration, frame rate, frame size, etc.",
|
"FFmpegTip": "FFprobe é um simples analisador de streams. Você pode usá-lo para extrar todos os tipos de informação sobre uma entrada incluindo duração, taxa de frames por segundo, tamanho do frame, etc;",
|
||||||
"Complete Stream URL": "URL de transmissão completa",
|
"Complete Stream URL": "URL de transmissão completa",
|
||||||
"ONVIF Scanner": "ONVIF Scanner",
|
"ONVIF Scanner": "ONVIF Scanner",
|
||||||
"Scan Settings": "Configurações de digitalização",
|
"Scan Settings": "Configurações de digitalização",
|
||||||
|
|
@ -162,7 +162,7 @@
|
||||||
"Browser Console Log": "Navegador de logs",
|
"Browser Console Log": "Navegador de logs",
|
||||||
"All Monitors and Privileges": "Todos monitores e privilégios",
|
"All Monitors and Privileges": "Todos monitores e privilégios",
|
||||||
"Permissions": "Permissões",
|
"Permissions": "Permissões",
|
||||||
"Time-lapse Tool": "Ferramenta Time-laps",
|
"Time-lapse Tool": "Time-lapse",
|
||||||
"total": "total",
|
"total": "total",
|
||||||
"MB": "MB",
|
"MB": "MB",
|
||||||
"Calendar": "Calendário",
|
"Calendar": "Calendário",
|
||||||
|
|
@ -187,11 +187,11 @@
|
||||||
"Add New": "Adicionar novo",
|
"Add New": "Adicionar novo",
|
||||||
"Delete Selected Videos": "Excluir vídeos selecionados",
|
"Delete Selected Videos": "Excluir vídeos selecionados",
|
||||||
"DeleteSelectedVideosMsg": "Deseja excluir esses vídeos? Você não poderá recuperá-los.",
|
"DeleteSelectedVideosMsg": "Deseja excluir esses vídeos? Você não poderá recuperá-los.",
|
||||||
"clientStreamFailedattemptingReconnect": "A verificação ctream do lado do cliente falhou, tentando reconectar.",
|
"clientStreamFailedattemptingReconnect": "A verificação do stream falhou, tentando se reconectar.",
|
||||||
"Delete Filter": "Excluir filtro",
|
"Delete Filter": "Excluir filtro",
|
||||||
"confirmDeleteFilter": "Deseja excluir este filtro? Você não poderá recuperá-lo.",
|
"confirmDeleteFilter": "Deseja excluir este filtro? Você não poderá recuperá-lo.",
|
||||||
"Fix Video": "Corrigir Vídeo",
|
"Fix Video": "Corrigir Vídeo",
|
||||||
"FixVideoMsg": "Você deseja corrigir esse vídeo? Você não poderá desfazer essa ação..",
|
"FixVideoMsg": "Você deseja corrigir o vídeo? Você não poderá desfazer essa ação..",
|
||||||
"DeleteVideoMsg": "Deseja excluir este vídeo? Você não poderá recuperá-lo.",
|
"DeleteVideoMsg": "Deseja excluir este vídeo? Você não poderá recuperá-lo.",
|
||||||
"dropBoxSuccess": "Sucesso! Arquivos salvos em seu Dropbox.",
|
"dropBoxSuccess": "Sucesso! Arquivos salvos em seu Dropbox.",
|
||||||
"API Key Deleted": "Chave da API excluída",
|
"API Key Deleted": "Chave da API excluída",
|
||||||
|
|
@ -246,13 +246,13 @@
|
||||||
"Connected": "Conectado",
|
"Connected": "Conectado",
|
||||||
"Not Connected": "Não conectado",
|
"Not Connected": "Não conectado",
|
||||||
"Lisence Plate Detector": "Detector de placas",
|
"Lisence Plate Detector": "Detector de placas",
|
||||||
"OpenCV Cascades": "OpenCV Cascatas",
|
"OpenCV Cascades": "Cascatas OpenCV ",
|
||||||
"Refresh List of Cascades": "Atualizar Lista de Cascatas",
|
"Refresh List of Cascades": "Atualizar Lista de Cascatas",
|
||||||
"\"No Motion\" Detector": "\"Sem movimento\" Detector",
|
"\"No Motion\" Detector": "\"Sem movimento\" Detector",
|
||||||
"Control": "Controle",
|
"Control": "Controle",
|
||||||
"Grouping": "Agrupando <small>Adicione grupos em <b>Configurações</b></small>",
|
"Grouping": "Agrupando <small>Adicione grupos em <b>Configurações</b></small>",
|
||||||
"Logging": "Logging",
|
"Logging": "Logging",
|
||||||
"IdentityText1": "É assim que o sistema irá identificar os dados para este fluxo. Você não pode alterar o <b>ID do Monitor</b> uma vez que você pressionou salvar. Se você quiser, você pode fazer o <b>ID do Monitor</b> mais legível para humanos antes de continuar.",
|
"IdentityText1": "É assim que o sistema irá identificar os dados para este fluxo. Você não pode alterar o <b>ID do Monitor</b> uma vez que você o salvou. Se você quiser, você pode fazer o <b>ID do Monitor</b> mais legível para humanos antes de continuar.",
|
||||||
"IdentityText2": "Você pode duplicar um monitor modificando o <b>ID do Monitor</b> e depois pressionando salvar. Você <b>não pode</b> usar o ID de um monitor que já existe ou ele economizará sobre as informações do banco de dados desse monitor.",
|
"IdentityText2": "Você pode duplicar um monitor modificando o <b>ID do Monitor</b> e depois pressionando salvar. Você <b>não pode</b> usar o ID de um monitor que já existe ou ele economizará sobre as informações do banco de dados desse monitor.",
|
||||||
"noSpecialCharacters": "Sem espaços ou caracteres especiais.",
|
"noSpecialCharacters": "Sem espaços ou caracteres especiais.",
|
||||||
"NotesPlacholder": "Comentários que você quer deixar para as configurações desta câmera.",
|
"NotesPlacholder": "Comentários que você quer deixar para as configurações desta câmera.",
|
||||||
|
|
@ -273,7 +273,7 @@
|
||||||
"Path": "Caminho",
|
"Path": "Caminho",
|
||||||
"Monitor Capture Rate": "Taxa de captura do monitor <small>(FPS)</small>",
|
"Monitor Capture Rate": "Taxa de captura do monitor <small>(FPS)</small>",
|
||||||
"Analyzation Duration": "Duração da análise",
|
"Analyzation Duration": "Duração da análise",
|
||||||
"Probe Size": "Probe Size",
|
"Probe Size": "Tamanho da sonda",
|
||||||
"Stream Type": "Tipo de transmissão",
|
"Stream Type": "Tipo de transmissão",
|
||||||
"# of Allow MJPEG Clients": "# para permitir clientes MJPEG <small>0 para infinito</small>",
|
"# of Allow MJPEG Clients": "# para permitir clientes MJPEG <small>0 para infinito</small>",
|
||||||
"HLS Video Encoder": "Codificador de vídeo HLS",
|
"HLS Video Encoder": "Codificador de vídeo HLS",
|
||||||
|
|
@ -281,9 +281,9 @@
|
||||||
"HLS Segment Length": "Comprimento do segmento HLS <small>em segundos</small>",
|
"HLS Segment Length": "Comprimento do segmento HLS <small>em segundos</small>",
|
||||||
"HLS Preset": "Pré-definição HLS",
|
"HLS Preset": "Pré-definição HLS",
|
||||||
"HLS List Size": "Tamanho da lista HLS",
|
"HLS List Size": "Tamanho da lista HLS",
|
||||||
"Check Signal Interval": "Verifique o intervalo do sinal <small>em minutos</small>",
|
"Check Signal Interval": "Verificar o intervalo do sinal <small>em minutos</small>",
|
||||||
"Log Signal Event": "Evento de sinal de registro <small>Apenas cliente</small>",
|
"Log Signal Event": "Evento de sinal de registro <small>Apenas cliente</small>",
|
||||||
"Quality": "Qualidade <small>1 para alta, 23 para Low</small>",
|
"Quality": "Qualidade <small>1 para alta, 23 para baixa</small>",
|
||||||
"Rate": "Taxa <small>(FPS)</small>",
|
"Rate": "Taxa <small>(FPS)</small>",
|
||||||
"Width": "Largura",
|
"Width": "Largura",
|
||||||
"Height": "Altura",
|
"Height": "Altura",
|
||||||
|
|
@ -444,13 +444,13 @@
|
||||||
"Process Unexpected Exit": "Saída inesperado do processo",
|
"Process Unexpected Exit": "Saída inesperado do processo",
|
||||||
"Process Crashed for Monitor": "Processo de Monitor quebrado",
|
"Process Crashed for Monitor": "Processo de Monitor quebrado",
|
||||||
"FFmpegCantStart": "FFmpeg não pôde iniciar",
|
"FFmpegCantStart": "FFmpeg não pôde iniciar",
|
||||||
"FFmpegCantStartText": "O mecanismo de gravação para esta câmera não pôde começar. Pode haver algo errado com a configuração da sua câmera. Se houver algum registro diferente deste, por favor, coloque-os em <b> Problemas </b> no Github.",
|
"FFmpegCantStartText": "O FFMpeg não pode inicializar. Pode haver algo de errado com a configuração da sua câmera. Se houver alguma configuração diferente, por favor, reporte no Gitlab.",
|
||||||
"JPEG Error": "Erro JPEG",
|
"JPEG Error": "Erro JPEG",
|
||||||
"JPEGErrorText": "Houve um problema ao obter dados da sua câmera.",
|
"JPEGErrorText": "Houve um problema ao obter dados da sua câmera.",
|
||||||
"Fatal Maximum Reached": "Máximo atingido, parando câmera.",
|
"Fatal Maximum Reached": "Máximo atingido, parando câmera.",
|
||||||
"FatalMaximumReachedText": "Erro JPEG fatal.",
|
"FatalMaximumReachedText": "Erro fatal de JPEG",
|
||||||
"Incorrect Settings Chosen": "Configuração incorreta escolhida",
|
"Incorrect Settings Chosen": "Configuração incorreta escolhida",
|
||||||
"Can't Connect": "Não pode conectar",
|
"Can't Connect": "Não foi possível conectar",
|
||||||
"Video Finished": "Vídeo finalizado",
|
"Video Finished": "Vídeo finalizado",
|
||||||
"No Monitor Found, Ignoring Request": "Monitor não encontrado, ignorando requisição",
|
"No Monitor Found, Ignoring Request": "Monitor não encontrado, ignorando requisição",
|
||||||
"Event": "Evento",
|
"Event": "Evento",
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,9 @@ const initialize = (config,lang) => {
|
||||||
connectionToP2PServer.on('disconnect',onDisconnect)
|
connectionToP2PServer.on('disconnect',onDisconnect)
|
||||||
}
|
}
|
||||||
startBridge()
|
startBridge()
|
||||||
setInterval(() => {
|
setInterval(function(){
|
||||||
startBridge(true)
|
if(!connectionToP2PServer || !connectionToP2PServer.connected){
|
||||||
},1000 * 60 * 60 * 15)
|
connectionToP2PServer.connect()
|
||||||
|
}
|
||||||
|
},1000 * 60 * 15)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,13 +123,24 @@ module.exports = function(s,config,lang,app,io){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getSnapshotFromOnvif(onvifOptions){
|
async function getSnapshotFromOnvif(onvifOptions){
|
||||||
return await createSnapshot({
|
let theUrl;
|
||||||
output: ['-s 400x400'],
|
if(onvifOptions.mid && onvifOptions.ke){
|
||||||
url: addCredentialsToStreamLink({
|
const groupKey = onvifOptions.ke
|
||||||
|
const monitorId = onvifOptions.mid
|
||||||
|
const theDevice = s.group[groupKey].activeMonitors[monitorId].onvifConnection
|
||||||
|
theUrl = (await theDevice.services.media.getSnapshotUri({
|
||||||
|
ProfileToken : theDevice.current_profile.token,
|
||||||
|
})).GetSnapshotUriResponse.MediaUri.Uri;
|
||||||
|
}else{
|
||||||
|
theUrl = addCredentialsToStreamLink({
|
||||||
username: onvifOptions.username,
|
username: onvifOptions.username,
|
||||||
password: onvifOptions.password,
|
password: onvifOptions.password,
|
||||||
url: onvifOptions.uri
|
url: onvifOptions.uri
|
||||||
}),
|
})
|
||||||
|
}
|
||||||
|
return await createSnapshot({
|
||||||
|
output: ['-s 400x400'],
|
||||||
|
url: theUrl,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,7 @@ module.exports = function(s,config){
|
||||||
]
|
]
|
||||||
const monitorRestrictions = options.monitorRestrictions
|
const monitorRestrictions = options.monitorRestrictions
|
||||||
var frameLimit = options.limit
|
var frameLimit = options.limit
|
||||||
|
const noLimit = options.noLimit === '1'
|
||||||
const endIsStartTo = options.endIsStartTo
|
const endIsStartTo = options.endIsStartTo
|
||||||
const chosenDate = options.date
|
const chosenDate = options.date
|
||||||
const startDate = options.startDate ? stringToSqlTime(options.startDate) : null
|
const startDate = options.startDate ? stringToSqlTime(options.startDate) : null
|
||||||
|
|
@ -217,6 +218,7 @@ module.exports = function(s,config){
|
||||||
whereQuery.push(['filename','=',options.filename])
|
whereQuery.push(['filename','=',options.filename])
|
||||||
frameLimit = "1";
|
frameLimit = "1";
|
||||||
}
|
}
|
||||||
|
if(noLimit)frameLimit = '0';
|
||||||
options.orderBy = options.orderBy ? options.orderBy : ['time','desc']
|
options.orderBy = options.orderBy ? options.orderBy : ['time','desc']
|
||||||
if(options.count)options.groupBy = options.groupBy ? options.groupBy : options.orderBy[0]
|
if(options.count)options.groupBy = options.groupBy ? options.groupBy : options.orderBy[0]
|
||||||
knexQuery({
|
knexQuery({
|
||||||
|
|
@ -337,7 +339,7 @@ module.exports = function(s,config){
|
||||||
endDate: endTime,
|
endDate: endTime,
|
||||||
startOperator: startTimeOperator,
|
startOperator: startTimeOperator,
|
||||||
endOperator: endTimeOperator,
|
endOperator: endTimeOperator,
|
||||||
limit: options.limit,
|
limit: options.noLimit === '1' ? '0' : options.limit,
|
||||||
archived: archived,
|
archived: archived,
|
||||||
rowType: rowName,
|
rowType: rowName,
|
||||||
endIsStartTo: endIsStartTo
|
endIsStartTo: endIsStartTo
|
||||||
|
|
|
||||||
|
|
@ -628,7 +628,7 @@ module.exports = (s,config,lang,app,io) => {
|
||||||
save : false,
|
save : false,
|
||||||
webhook : false,
|
webhook : false,
|
||||||
command : false,
|
command : false,
|
||||||
record : false,
|
record : true,
|
||||||
forceRecord : false,
|
forceRecord : false,
|
||||||
indifference : false,
|
indifference : false,
|
||||||
countObjects : false
|
countObjects : false
|
||||||
|
|
@ -646,8 +646,7 @@ module.exports = (s,config,lang,app,io) => {
|
||||||
})
|
})
|
||||||
const eventDetails = d.details
|
const eventDetails = d.details
|
||||||
const passedEventFilters = checkEventFilters(d,monitorDetails,filter)
|
const passedEventFilters = checkEventFilters(d,monitorDetails,filter)
|
||||||
if(!passedEventFilters)return
|
if(!passedEventFilters)return;
|
||||||
const detailString = JSON.stringify(eventDetails)
|
|
||||||
const eventTime = new Date()
|
const eventTime = new Date()
|
||||||
if(
|
if(
|
||||||
filter.addToMotionCounter &&
|
filter.addToMotionCounter &&
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,7 @@ module.exports = async (s,config,lang,onFinish) => {
|
||||||
mid: e.mid,
|
mid: e.mid,
|
||||||
}
|
}
|
||||||
const ffmpegCommand = [`-progress pipe:5`];
|
const ffmpegCommand = [`-progress pipe:5`];
|
||||||
([
|
const allOutputs = [
|
||||||
buildMainInput(e),
|
|
||||||
buildMainStream(e),
|
buildMainStream(e),
|
||||||
buildJpegApiOutput(e),
|
buildJpegApiOutput(e),
|
||||||
buildMainRecording(e),
|
buildMainRecording(e),
|
||||||
|
|
@ -43,21 +42,25 @@ module.exports = async (s,config,lang,onFinish) => {
|
||||||
buildMainDetector(e),
|
buildMainDetector(e),
|
||||||
buildEventRecordingOutput(e),
|
buildEventRecordingOutput(e),
|
||||||
buildTimelapseOutput(e),
|
buildTimelapseOutput(e),
|
||||||
]).forEach(function(commandStringPart){
|
];
|
||||||
ffmpegCommand.push(commandStringPart)
|
if(allOutputs.filter(output => !!output).length > 0){
|
||||||
})
|
([
|
||||||
s.onFfmpegCameraStringCreationExtensions.forEach(function(extender){
|
buildMainInput(e),
|
||||||
extender(e,ffmpegCommand)
|
]).concat(allOutputs).forEach(function(commandStringPart){
|
||||||
})
|
ffmpegCommand.push(commandStringPart)
|
||||||
const stdioPipes = createPipeArray(e)
|
})
|
||||||
const ffmpegCommandString = ffmpegCommand.join(' ')
|
s.onFfmpegCameraStringCreationExtensions.forEach(function(extender){
|
||||||
//hold ffmpeg command for log stream
|
extender(e,ffmpegCommand)
|
||||||
s.group[e.ke].activeMonitors[e.mid].ffmpeg = sanitizedFfmpegCommand(e,ffmpegCommandString)
|
})
|
||||||
//clean the string of spatial impurities and split for spawn()
|
const stdioPipes = createPipeArray(e)
|
||||||
const ffmpegCommandParsed = splitForFFPMEG(ffmpegCommandString)
|
const ffmpegCommandString = ffmpegCommand.join(' ')
|
||||||
try{
|
//hold ffmpeg command for log stream
|
||||||
fs.unlinkSync(e.sdir + 'cmd.txt')
|
activeMonitor.ffmpeg = sanitizedFfmpegCommand(e,ffmpegCommandString)
|
||||||
}catch(err){
|
//clean the string of spatial impurities and split for spawn()
|
||||||
|
const ffmpegCommandParsed = splitForFFPMEG(ffmpegCommandString)
|
||||||
|
try{
|
||||||
|
fs.unlinkSync(e.sdir + 'cmd.txt')
|
||||||
|
}catch(err){
|
||||||
|
|
||||||
}
|
}
|
||||||
fs.writeFileSync(e.sdir + 'cmd.txt',JSON.stringify({
|
fs.writeFileSync(e.sdir + 'cmd.txt',JSON.stringify({
|
||||||
|
|
@ -69,20 +72,31 @@ module.exports = async (s,config,lang,onFinish) => {
|
||||||
config: config,
|
config: config,
|
||||||
isAtleatOneDetectorPluginConnected: s.isAtleatOneDetectorPluginConnected
|
isAtleatOneDetectorPluginConnected: s.isAtleatOneDetectorPluginConnected
|
||||||
}
|
}
|
||||||
},null,3),'utf8')
|
fs.writeFileSync(e.sdir + 'cmd.txt',JSON.stringify({
|
||||||
var cameraCommandParams = [
|
cmd: ffmpegCommandParsed,
|
||||||
config.monitorDaemonPath ? config.monitorDaemonPath : __dirname + '/cameraThread/singleCamera.js',
|
pipes: stdioPipes.length,
|
||||||
config.ffmpegDir,
|
rawMonitorConfig: s.group[e.ke].rawMonitorConfigurations[e.id],
|
||||||
e.sdir + 'cmd.txt'
|
globalInfo: {
|
||||||
]
|
config: config,
|
||||||
const cameraProcess = spawn('node',cameraCommandParams,{detached: true,stdio: stdioPipes})
|
isAtleatOneDetectorPluginConnected: s.isAtleatOneDetectorPluginConnected
|
||||||
if(config.debugLog === true && config.debugLogMonitors === true){
|
}
|
||||||
cameraProcess.stderr.on('data',(data) => {
|
},null,3),'utf8')
|
||||||
console.log(`${e.ke} ${e.mid}`)
|
var cameraCommandParams = [
|
||||||
console.log(data.toString())
|
config.monitorDaemonPath ? config.monitorDaemonPath : __dirname + '/cameraThread/singleCamera.js',
|
||||||
})
|
config.ffmpegDir,
|
||||||
|
e.sdir + 'cmd.txt'
|
||||||
|
]
|
||||||
|
const cameraProcess = spawn('node',cameraCommandParams,{detached: true,stdio: stdioPipes})
|
||||||
|
if(config.debugLog === true && config.debugLogMonitors === true){
|
||||||
|
cameraProcess.stderr.on('data',(data) => {
|
||||||
|
console.log(`${e.ke} ${e.mid}`)
|
||||||
|
console.log(data.toString())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return cameraProcess
|
||||||
|
}else{
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
return cameraProcess
|
|
||||||
}catch(err){
|
}catch(err){
|
||||||
s.systemLog(err)
|
s.systemLog(err)
|
||||||
return null
|
return null
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ module.exports = (s,config,lang) => {
|
||||||
const createStreamChannel = function(e,number,channel){
|
const createStreamChannel = function(e,number,channel){
|
||||||
//`e` is the monitor object
|
//`e` is the monitor object
|
||||||
//`x` is an object used to contain temporary values.
|
//`x` is an object used to contain temporary values.
|
||||||
const channelStreamDirectory = !isNaN(parseInt(number)) ? `${e.sdir}channel${number}/` : e.sdir
|
const channelStreamDirectory = !isNaN(parseInt(number)) ? `${e.sdir || s.getStreamsDirectory(e)}channel${number}/` : e.sdir
|
||||||
if(channelStreamDirectory !== e.sdir && !fs.existsSync(channelStreamDirectory)){
|
if(channelStreamDirectory !== e.sdir && !fs.existsSync(channelStreamDirectory)){
|
||||||
try{
|
try{
|
||||||
fs.mkdirSync(channelStreamDirectory)
|
fs.mkdirSync(channelStreamDirectory)
|
||||||
|
|
@ -377,7 +377,7 @@ module.exports = (s,config,lang) => {
|
||||||
//x = temporary values
|
//x = temporary values
|
||||||
const streamFlags = []
|
const streamFlags = []
|
||||||
const streamType = e.details.stream_type ? e.details.stream_type : 'hls'
|
const streamType = e.details.stream_type ? e.details.stream_type : 'hls'
|
||||||
if(streamType !== 'jpeg'){
|
if(streamType !== 'jpeg' && streamType !== 'useSubstream'){
|
||||||
const isCudaEnabled = hasCudaEnabled(e)
|
const isCudaEnabled = hasCudaEnabled(e)
|
||||||
const streamFilters = []
|
const streamFilters = []
|
||||||
const videoCodecisCopy = e.details.stream_vcodec === 'copy'
|
const videoCodecisCopy = e.details.stream_vcodec === 'copy'
|
||||||
|
|
|
||||||
169
libs/monitor.js
169
libs/monitor.js
|
|
@ -35,6 +35,10 @@ module.exports = function(s,config,lang){
|
||||||
cameraDestroy,
|
cameraDestroy,
|
||||||
monitorConfigurationMigrator,
|
monitorConfigurationMigrator,
|
||||||
attachStreamChannelHandlers,
|
attachStreamChannelHandlers,
|
||||||
|
setActiveViewer,
|
||||||
|
getActiveViewerCount,
|
||||||
|
destroySubstreamProcess,
|
||||||
|
attachMainProcessHandlers,
|
||||||
} = require('./monitor/utils.js')(s,config,lang)
|
} = require('./monitor/utils.js')(s,config,lang)
|
||||||
const {
|
const {
|
||||||
addEventDetailsToString,
|
addEventDetailsToString,
|
||||||
|
|
@ -66,7 +70,7 @@ module.exports = function(s,config,lang){
|
||||||
if(!activeMonitor.contentWriter){activeMonitor.contentWriter={}};
|
if(!activeMonitor.contentWriter){activeMonitor.contentWriter={}};
|
||||||
if(!activeMonitor.childNodeStreamWriters){activeMonitor.childNodeStreamWriters={}};
|
if(!activeMonitor.childNodeStreamWriters){activeMonitor.childNodeStreamWriters={}};
|
||||||
if(!activeMonitor.eventBasedRecording){activeMonitor.eventBasedRecording={}};
|
if(!activeMonitor.eventBasedRecording){activeMonitor.eventBasedRecording={}};
|
||||||
if(!activeMonitor.watch){activeMonitor.watch={}};
|
if(!activeMonitor.watch){activeMonitor.watch = []};
|
||||||
if(!activeMonitor.fixingVideos){activeMonitor.fixingVideos={}};
|
if(!activeMonitor.fixingVideos){activeMonitor.fixingVideos={}};
|
||||||
// if(!activeMonitor.viewerConnection){activeMonitor.viewerConnection={}};
|
// if(!activeMonitor.viewerConnection){activeMonitor.viewerConnection={}};
|
||||||
// if(!activeMonitor.viewerConnectionCount){activeMonitor.viewerConnectionCount=0};
|
// if(!activeMonitor.viewerConnectionCount){activeMonitor.viewerConnectionCount=0};
|
||||||
|
|
@ -151,7 +155,7 @@ module.exports = function(s,config,lang){
|
||||||
return x.ar;
|
return x.ar;
|
||||||
}
|
}
|
||||||
s.getStreamsDirectory = (monitor) => {
|
s.getStreamsDirectory = (monitor) => {
|
||||||
return s.dir.streams + monitor.ke + '/' + monitor.mid + '/'
|
return s.dir.streams + monitor.ke + '/' + (monitor.mid || monitor.id) + '/'
|
||||||
}
|
}
|
||||||
s.getRawSnapshotFromMonitor = function(monitor,options){
|
s.getRawSnapshotFromMonitor = function(monitor,options){
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
|
|
@ -513,20 +517,7 @@ module.exports = function(s,config,lang){
|
||||||
s.checkDetails(e)
|
s.checkDetails(e)
|
||||||
if(e.ke && config.doSnapshot === true){
|
if(e.ke && config.doSnapshot === true){
|
||||||
if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
|
if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
|
||||||
if(s.group[e.ke].activeMonitors[e.mid].onvifConnection){
|
async function getRaw(){
|
||||||
const screenShot = await s.getSnapshotFromOnvif({
|
|
||||||
username: onvifUsername,
|
|
||||||
password: onvifPassword,
|
|
||||||
uri: cameraResponse.uri,
|
|
||||||
});
|
|
||||||
s.tx({
|
|
||||||
f: 'monitor_snapshot',
|
|
||||||
snapshot: screenShot.toString('base64'),
|
|
||||||
snapshot_format: 'b64',
|
|
||||||
mid: e.mid,
|
|
||||||
ke: e.ke
|
|
||||||
},'GRP_'+e.ke)
|
|
||||||
}else{
|
|
||||||
var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
|
var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
|
||||||
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
|
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
|
||||||
if(screenShot){
|
if(screenShot){
|
||||||
|
|
@ -540,7 +531,27 @@ module.exports = function(s,config,lang){
|
||||||
}else{
|
}else{
|
||||||
s.debugLog('Damaged Snapshot Data')
|
s.debugLog('Damaged Snapshot Data')
|
||||||
s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if(s.group[e.ke].activeMonitors[e.mid].onvifConnection){
|
||||||
|
try{
|
||||||
|
const screenShot = await s.getSnapshotFromOnvif({
|
||||||
|
ke: e.ke,
|
||||||
|
mid: e.mid,
|
||||||
|
});
|
||||||
|
s.tx({
|
||||||
|
f: 'monitor_snapshot',
|
||||||
|
snapshot: screenShot.toString('base64'),
|
||||||
|
snapshot_format: 'b64',
|
||||||
|
mid: e.mid,
|
||||||
|
ke: e.ke
|
||||||
|
},'GRP_'+e.ke)
|
||||||
|
}catch(err){
|
||||||
|
s.debugLog(err)
|
||||||
|
await getRaw()
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
await getRaw()
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
s.tx({f:'monitor_snapshot',snapshot:'Disabled',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
s.tx({f:'monitor_snapshot',snapshot:'Disabled',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
||||||
|
|
@ -765,55 +776,8 @@ module.exports = function(s,config,lang){
|
||||||
code: e.wantedStatusCode
|
code: e.wantedStatusCode
|
||||||
});
|
});
|
||||||
//on unexpected exit restart
|
//on unexpected exit restart
|
||||||
s.group[e.ke].activeMonitors[e.id].spawn_exit = function(){
|
if(s.group[e.ke].activeMonitors[e.id].spawn)attachMainProcessHandlers(e,fatalError)
|
||||||
if(s.group[e.ke].activeMonitors[e.id].isStarted === true){
|
return s.group[e.ke].activeMonitors[e.id].spawn
|
||||||
if(e.details.loglevel!=='quiet'){
|
|
||||||
s.userLog(e,{type:lang['Process Unexpected Exit'],msg:{msg:lang.unexpectedExitText,cmd:s.group[e.ke].activeMonitors[e.id].ffmpeg}});
|
|
||||||
}
|
|
||||||
fatalError(e,'Process Unexpected Exit');
|
|
||||||
scanForOrphanedVideos(e,{
|
|
||||||
forceCheck: true,
|
|
||||||
checkMax: 2
|
|
||||||
})
|
|
||||||
s.onMonitorUnexpectedExitExtensions.forEach(function(extender){
|
|
||||||
extender(Object.assign(s.group[e.ke].rawMonitorConfigurations[e.id],{}),e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.group[e.ke].activeMonitors[e.id].spawn.on('end',s.group[e.ke].activeMonitors[e.id].spawn_exit)
|
|
||||||
s.group[e.ke].activeMonitors[e.id].spawn.on('exit',s.group[e.ke].activeMonitors[e.id].spawn_exit)
|
|
||||||
s.group[e.ke].activeMonitors[e.id].spawn.on('error',function(er){
|
|
||||||
s.userLog(e,{type:'Spawn Error',msg:er});fatalError(e,'Spawn Error')
|
|
||||||
})
|
|
||||||
s.userLog(e,{type:lang['Process Started'],msg:{cmd:s.group[e.ke].activeMonitors[e.id].ffmpeg}})
|
|
||||||
if(s.isWin === false){
|
|
||||||
var strippedHost = s.stripAuthFromHost(e)
|
|
||||||
var sendProcessCpuUsage = function(){
|
|
||||||
s.getMonitorCpuUsage(e,function(percent){
|
|
||||||
s.group[e.ke].activeMonitors[e.id].currentCpuUsage = percent
|
|
||||||
s.tx({
|
|
||||||
f: 'camera_cpu_usage',
|
|
||||||
ke: e.ke,
|
|
||||||
id: e.id,
|
|
||||||
percent: percent
|
|
||||||
},'MON_STREAM_'+e.ke+e.id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
clearInterval(s.group[e.ke].activeMonitors[e.id].getMonitorCpuUsage)
|
|
||||||
s.group[e.ke].activeMonitors[e.id].getMonitorCpuUsage = setInterval(function(){
|
|
||||||
if(e.details.skip_ping !== '1'){
|
|
||||||
connectionTester.test(strippedHost,e.port,2000,function(err,response){
|
|
||||||
if(response.success){
|
|
||||||
sendProcessCpuUsage()
|
|
||||||
}else{
|
|
||||||
launchMonitorProcesses(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}else{
|
|
||||||
sendProcessCpuUsage()
|
|
||||||
}
|
|
||||||
},1000 * 60)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const createEventCounter = function(monitor){
|
const createEventCounter = function(monitor){
|
||||||
if(monitor.details.detector_obj_count === '1'){
|
if(monitor.details.detector_obj_count === '1'){
|
||||||
|
|
@ -1235,27 +1199,29 @@ module.exports = function(s,config,lang){
|
||||||
if(pingResponse.success === true){
|
if(pingResponse.success === true){
|
||||||
activeMonitor.isRecording = true
|
activeMonitor.isRecording = true
|
||||||
try{
|
try{
|
||||||
createCameraFfmpegProcess(e)
|
var mainProcess = createCameraFfmpegProcess(e)
|
||||||
createCameraStreamHandlers(e)
|
|
||||||
createEventCounter(e)
|
createEventCounter(e)
|
||||||
if(e.type === 'dashcam' || e.type === 'socket'){
|
if(mainProcess){
|
||||||
setTimeout(function(){
|
createCameraStreamHandlers(e)
|
||||||
activeMonitor.allowStdinWrite = true
|
if(e.type === 'dashcam' || e.type === 'socket'){
|
||||||
s.txToDashcamUsers({
|
setTimeout(function(){
|
||||||
f : 'enable_stream',
|
activeMonitor.allowStdinWrite = true
|
||||||
ke : e.ke,
|
s.txToDashcamUsers({
|
||||||
mid : e.id
|
f : 'enable_stream',
|
||||||
},e.ke)
|
ke : e.ke,
|
||||||
},30000)
|
mid : e.id
|
||||||
}
|
},e.ke)
|
||||||
if(
|
},30000)
|
||||||
e.functionMode === 'record' ||
|
}
|
||||||
e.type === 'mjpeg' ||
|
if(
|
||||||
e.type === 'h264' ||
|
e.functionMode === 'record' ||
|
||||||
e.type === 'local'
|
e.type === 'mjpeg' ||
|
||||||
){
|
e.type === 'h264' ||
|
||||||
catchNewSegmentNames(e)
|
e.type === 'local'
|
||||||
cameraFilterFfmpegLog(e)
|
){
|
||||||
|
catchNewSegmentNames(e)
|
||||||
|
cameraFilterFfmpegLog(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
clearTimeout(activeMonitor.onMonitorStartTimer)
|
clearTimeout(activeMonitor.onMonitorStartTimer)
|
||||||
activeMonitor.onMonitorStartTimer = setTimeout(() => {
|
activeMonitor.onMonitorStartTimer = setTimeout(() => {
|
||||||
|
|
@ -1511,26 +1477,23 @@ module.exports = function(s,config,lang){
|
||||||
s.initiateMonitorObject({ke:e.ke,mid:e.id})
|
s.initiateMonitorObject({ke:e.ke,mid:e.id})
|
||||||
switch(e.functionMode){
|
switch(e.functionMode){
|
||||||
case'watch_on'://live streamers - join
|
case'watch_on'://live streamers - join
|
||||||
if(!cn.monitorsCurrentlyWatching){cn.monitorsCurrentlyWatching = {}}
|
if(!cn.monitorsCurrentlyWatching){cn.monitorsCurrentlyWatching = {}}
|
||||||
if(!cn.monitorsCurrentlyWatching[e.id]){cn.monitorsCurrentlyWatching[e.id]={ke:e.ke}}
|
if(!cn.monitorsCurrentlyWatching[e.id]){cn.monitorsCurrentlyWatching[e.id]={ke:e.ke}}
|
||||||
s.group[e.ke].activeMonitors[e.id].watch[cn.id]={};
|
setActiveViewer(e.ke,e.id,cn.id,true)
|
||||||
var numberOfViewers = Object.keys(s.group[e.ke].activeMonitors[e.id].watch).length
|
s.group[e.ke].activeMonitors[e.id].allowDestroySubstream = false
|
||||||
s.tx({
|
clearTimeout(s.group[e.ke].activeMonitors[e.id].noViewerCountDisableSubstream)
|
||||||
viewers: numberOfViewers,
|
|
||||||
ke: e.ke,
|
|
||||||
id: e.id
|
|
||||||
},'MON_'+e.ke+e.id)
|
|
||||||
break;
|
break;
|
||||||
case'watch_off'://live streamers - leave
|
case'watch_off'://live streamers - leave
|
||||||
if(cn.monitorsCurrentlyWatching){delete(cn.monitorsCurrentlyWatching[e.id])}
|
if(cn.monitorsCurrentlyWatching){delete(cn.monitorsCurrentlyWatching[e.id])}
|
||||||
var numberOfViewers = 0
|
setActiveViewer(e.ke,e.id,cn.id,false)
|
||||||
delete(s.group[e.ke].activeMonitors[e.id].watch[cn.id]);
|
clearTimeout(s.group[e.ke].activeMonitors[e.id].noViewerCountDisableSubstream)
|
||||||
numberOfViewers = Object.keys(s.group[e.ke].activeMonitors[e.id].watch).length
|
s.group[e.ke].activeMonitors[e.id].noViewerCountDisableSubstream = setTimeout(async () => {
|
||||||
s.tx({
|
let currentCount = getActiveViewerCount(e.ke,e.id)
|
||||||
viewers: numberOfViewers,
|
if(currentCount === 0 && s.group[e.ke].activeMonitors[e.id].subStreamProcess){
|
||||||
ke: e.ke,
|
s.group[e.ke].activeMonitors[e.id].allowDestroySubstream = true
|
||||||
id: e.id
|
await destroySubstreamProcess(s.group[e.ke].activeMonitors[e.id])
|
||||||
},'MON_'+e.ke+e.id)
|
}
|
||||||
|
},10000)
|
||||||
break;
|
break;
|
||||||
case'restart'://restart monitor
|
case'restart'://restart monitor
|
||||||
s.sendMonitorStatus({
|
s.sendMonitorStatus({
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ const treekill = require('tree-kill');
|
||||||
const spawn = require('child_process').spawn;
|
const spawn = require('child_process').spawn;
|
||||||
const events = require('events');
|
const events = require('events');
|
||||||
const Mp4Frag = require('mp4frag');
|
const Mp4Frag = require('mp4frag');
|
||||||
|
const streamViewerCountTimeouts = {}
|
||||||
module.exports = (s,config,lang) => {
|
module.exports = (s,config,lang) => {
|
||||||
|
const {
|
||||||
|
scanForOrphanedVideos
|
||||||
|
} = require('../video/utils.js')(s,config,lang)
|
||||||
const {
|
const {
|
||||||
createPipeArray,
|
createPipeArray,
|
||||||
splitForFFPMEG,
|
splitForFFPMEG,
|
||||||
|
|
@ -17,6 +21,10 @@ module.exports = (s,config,lang) => {
|
||||||
const processKill = (proc) => {
|
const processKill = (proc) => {
|
||||||
const response = {ok: true}
|
const response = {ok: true}
|
||||||
return new Promise((resolve,reject) => {
|
return new Promise((resolve,reject) => {
|
||||||
|
if(!proc){
|
||||||
|
resolve(response)
|
||||||
|
return
|
||||||
|
}
|
||||||
function sendError(err){
|
function sendError(err){
|
||||||
response.ok = false
|
response.ok = false
|
||||||
response.err = err
|
response.err = err
|
||||||
|
|
@ -94,13 +102,17 @@ module.exports = (s,config,lang) => {
|
||||||
if(activeMonitor.onChildNodeExit){
|
if(activeMonitor.onChildNodeExit){
|
||||||
activeMonitor.onChildNodeExit()
|
activeMonitor.onChildNodeExit()
|
||||||
}
|
}
|
||||||
activeMonitor.spawn.stdio.forEach(function(stdio){
|
try{
|
||||||
try{
|
activeMonitor.spawn.stdio.forEach(function(stdio){
|
||||||
stdio.unpipe()
|
try{
|
||||||
}catch(err){
|
stdio.unpipe()
|
||||||
console.log(err)
|
}catch(err){
|
||||||
}
|
console.log(err)
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}catch(err){
|
||||||
|
// s.debugLog(err)
|
||||||
|
}
|
||||||
if(activeMonitor.mp4frag){
|
if(activeMonitor.mp4frag){
|
||||||
var mp4FragChannels = Object.keys(activeMonitor.mp4frag)
|
var mp4FragChannels = Object.keys(activeMonitor.mp4frag)
|
||||||
mp4FragChannels.forEach(function(channel){
|
mp4FragChannels.forEach(function(channel){
|
||||||
|
|
@ -116,6 +128,7 @@ module.exports = (s,config,lang) => {
|
||||||
}else{
|
}else{
|
||||||
processKill(proc).then((response) => {
|
processKill(proc).then((response) => {
|
||||||
s.debugLog(`cameraDestroy`,response)
|
s.debugLog(`cameraDestroy`,response)
|
||||||
|
activeMonitor.allowDestroySubstream = true
|
||||||
destroySubstreamProcess(activeMonitor).then((response) => {
|
destroySubstreamProcess(activeMonitor).then((response) => {
|
||||||
if(response.hadSubStream)s.debugLog(`cameraDestroy`,response.closeResponse)
|
if(response.hadSubStream)s.debugLog(`cameraDestroy`,response.closeResponse)
|
||||||
})
|
})
|
||||||
|
|
@ -209,13 +222,19 @@ module.exports = (s,config,lang) => {
|
||||||
const spawnSubstreamProcess = function(e){
|
const spawnSubstreamProcess = function(e){
|
||||||
// e = monitorConfig
|
// e = monitorConfig
|
||||||
try{
|
try{
|
||||||
const monitorConfig = s.group[e.ke].rawMonitorConfigurations[e.mid]
|
const groupKey = e.ke
|
||||||
|
const monitorId = e.mid
|
||||||
|
const monitorConfig = Object.assign({},s.group[groupKey].rawMonitorConfigurations[monitorId])
|
||||||
const monitorDetails = monitorConfig.details
|
const monitorDetails = monitorConfig.details
|
||||||
const activeMonitor = s.group[e.ke].activeMonitors[e.mid]
|
const activeMonitor = s.group[e.ke].activeMonitors[e.mid]
|
||||||
const channelNumber = 1 + (monitorDetails.stream_channels || []).length
|
const channelNumber = 1 + (monitorDetails.stream_channels || []).length
|
||||||
const ffmpegCommand = [`-progress pipe:5`];
|
const ffmpegCommand = [`-progress pipe:5`];
|
||||||
const logLevel = monitorDetails.loglevel ? e.details.loglevel : 'warning'
|
const logLevel = monitorDetails.loglevel ? e.details.loglevel : 'warning'
|
||||||
const stdioPipes = createPipeArray({}, 2)
|
const stdioPipes = createPipeArray({}, 2)
|
||||||
|
const substreamConfig = monitorConfig.details.substream
|
||||||
|
substreamConfig.input.type = !substreamConfig.input.fulladdress ? monitorConfig.type : substreamConfig.input.type || monitorConfig.details.rtsp_transport
|
||||||
|
substreamConfig.input.fulladdress = substreamConfig.input.fulladdress || s.buildMonitorUrl(monitorConfig)
|
||||||
|
substreamConfig.input.rtsp_transport = substreamConfig.input.rtsp_transport || monitorConfig.details.rtsp_transport
|
||||||
const {
|
const {
|
||||||
inputAndConnectionFields,
|
inputAndConnectionFields,
|
||||||
outputFields,
|
outputFields,
|
||||||
|
|
@ -259,8 +278,7 @@ module.exports = (s,config,lang) => {
|
||||||
s.userLog({
|
s.userLog({
|
||||||
ke: e.ke,
|
ke: e.ke,
|
||||||
mid: e.mid,
|
mid: e.mid,
|
||||||
},
|
},{
|
||||||
{
|
|
||||||
type: lang["Substream Process"],
|
type: lang["Substream Process"],
|
||||||
msg: data.toString()
|
msg: data.toString()
|
||||||
})
|
})
|
||||||
|
|
@ -302,21 +320,25 @@ module.exports = (s,config,lang) => {
|
||||||
hadSubStream: false,
|
hadSubStream: false,
|
||||||
alreadyClosing: false
|
alreadyClosing: false
|
||||||
}
|
}
|
||||||
if(activeMonitor.subStreamProcessClosing){
|
try{
|
||||||
response.alreadyClosing = true
|
if(activeMonitor.subStreamProcessClosing){
|
||||||
}else if(activeMonitor.subStreamProcess){
|
response.alreadyClosing = true
|
||||||
activeMonitor.subStreamProcessClosing = true
|
}else if(activeMonitor.subStreamProcess){
|
||||||
activeMonitor.subStreamChannel = null;
|
activeMonitor.subStreamProcessClosing = true
|
||||||
const closeResponse = await processKill(activeMonitor.subStreamProcess)
|
activeMonitor.subStreamChannel = null;
|
||||||
response.hadSubStream = true
|
const closeResponse = await processKill(activeMonitor.subStreamProcess)
|
||||||
response.closeResponse = closeResponse
|
response.hadSubStream = true
|
||||||
delete(activeMonitor.subStreamProcess)
|
response.closeResponse = closeResponse
|
||||||
s.tx({
|
delete(activeMonitor.subStreamProcess)
|
||||||
f: 'substream_end',
|
s.tx({
|
||||||
mid: activeMonitor.mid,
|
f: 'substream_end',
|
||||||
ke: activeMonitor.ke
|
mid: activeMonitor.mid,
|
||||||
},'GRP_'+activeMonitor.ke);
|
ke: activeMonitor.ke
|
||||||
activeMonitor.subStreamProcessClosing = false
|
},'GRP_'+activeMonitor.ke);
|
||||||
|
activeMonitor.subStreamProcessClosing = false
|
||||||
|
}
|
||||||
|
}catch(err){
|
||||||
|
s.debugLog('destroySubstreamProcess',err)
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
@ -360,6 +382,97 @@ module.exports = (s,config,lang) => {
|
||||||
ffmpegProcess.stdio[pipeNumber].on('data',frameToStreamAdded)
|
ffmpegProcess.stdio[pipeNumber].on('data',frameToStreamAdded)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function setActiveViewer(groupKey,monitorId,connectionId,isBeingAdded){
|
||||||
|
const viewerList = s.group[groupKey].activeMonitors[monitorId].watch;
|
||||||
|
if(isBeingAdded){
|
||||||
|
if(viewerList.indexOf(connectionId) > -1)viewerList.push(connectionId);
|
||||||
|
}else{
|
||||||
|
viewerList.splice(viewerList.indexOf(connectionId), 1)
|
||||||
|
}
|
||||||
|
const numberOfViewers = viewerList.length
|
||||||
|
s.tx({
|
||||||
|
f: 'viewer_count',
|
||||||
|
viewers: numberOfViewers,
|
||||||
|
ke: groupKey,
|
||||||
|
id: monitorId
|
||||||
|
},'MON_' + groupKey + monitorId)
|
||||||
|
return numberOfViewers;
|
||||||
|
}
|
||||||
|
function getActiveViewerCount(groupKey,monitorId){
|
||||||
|
const viewerList = s.group[groupKey].activeMonitors[monitorId].watch;
|
||||||
|
const numberOfViewers = viewerList.length
|
||||||
|
return numberOfViewers;
|
||||||
|
}
|
||||||
|
function setTimedActiveViewerForHttp(req){
|
||||||
|
const groupKey = req.params.ke
|
||||||
|
const connectionId = req.params.auth
|
||||||
|
const loggedInUser = s.group[groupKey].users[connectionId]
|
||||||
|
if(!loggedInUser){
|
||||||
|
const monitorId = req.params.id
|
||||||
|
const viewerList = s.group[groupKey].activeMonitors[monitorId].watch
|
||||||
|
const theViewer = viewerList[connectionId]
|
||||||
|
if(!theViewer){
|
||||||
|
setActiveViewer(groupKey,monitorId,connectionId,true)
|
||||||
|
}
|
||||||
|
clearTimeout(streamViewerCountTimeouts[req.originalUrl])
|
||||||
|
streamViewerCountTimeouts[req.originalUrl] = setTimeout(() => {
|
||||||
|
setActiveViewer(groupKey,monitorId,connectionId,false)
|
||||||
|
},5000)
|
||||||
|
}else{
|
||||||
|
s.debugLog(`User is Logged in, Don't add to viewer count`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function attachMainProcessHandlers(e,fatalError){
|
||||||
|
s.group[e.ke].activeMonitors[e.id].spawn_exit = function(){
|
||||||
|
if(s.group[e.ke].activeMonitors[e.id].isStarted === true){
|
||||||
|
if(e.details.loglevel!=='quiet'){
|
||||||
|
s.userLog(e,{type:lang['Process Unexpected Exit'],msg:{msg:lang.unexpectedExitText,cmd:s.group[e.ke].activeMonitors[e.id].ffmpeg}});
|
||||||
|
}
|
||||||
|
fatalError(e,'Process Unexpected Exit');
|
||||||
|
scanForOrphanedVideos(e,{
|
||||||
|
forceCheck: true,
|
||||||
|
checkMax: 2
|
||||||
|
})
|
||||||
|
s.onMonitorUnexpectedExitExtensions.forEach(function(extender){
|
||||||
|
extender(Object.assign(s.group[e.ke].rawMonitorConfigurations[e.id],{}),e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.group[e.ke].activeMonitors[e.id].spawn.on('end',s.group[e.ke].activeMonitors[e.id].spawn_exit)
|
||||||
|
s.group[e.ke].activeMonitors[e.id].spawn.on('exit',s.group[e.ke].activeMonitors[e.id].spawn_exit)
|
||||||
|
s.group[e.ke].activeMonitors[e.id].spawn.on('error',function(er){
|
||||||
|
s.userLog(e,{type:'Spawn Error',msg:er});fatalError(e,'Spawn Error')
|
||||||
|
})
|
||||||
|
s.userLog(e,{type:lang['Process Started'],msg:{cmd:s.group[e.ke].activeMonitors[e.id].ffmpeg}})
|
||||||
|
// if(s.isWin === false){
|
||||||
|
// var strippedHost = s.stripAuthFromHost(e)
|
||||||
|
// var sendProcessCpuUsage = function(){
|
||||||
|
// s.getMonitorCpuUsage(e,function(percent){
|
||||||
|
// s.group[e.ke].activeMonitors[e.id].currentCpuUsage = percent
|
||||||
|
// s.tx({
|
||||||
|
// f: 'camera_cpu_usage',
|
||||||
|
// ke: e.ke,
|
||||||
|
// id: e.id,
|
||||||
|
// percent: percent
|
||||||
|
// },'MON_STREAM_'+e.ke+e.id)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// clearInterval(s.group[e.ke].activeMonitors[e.id].getMonitorCpuUsage)
|
||||||
|
// s.group[e.ke].activeMonitors[e.id].getMonitorCpuUsage = setInterval(function(){
|
||||||
|
// if(e.details.skip_ping !== '1'){
|
||||||
|
// connectionTester.test(strippedHost,e.port,2000,function(err,response){
|
||||||
|
// if(response.success){
|
||||||
|
// sendProcessCpuUsage()
|
||||||
|
// }else{
|
||||||
|
// launchMonitorProcesses(e)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }else{
|
||||||
|
// sendProcessCpuUsage()
|
||||||
|
// }
|
||||||
|
// },1000 * 60)
|
||||||
|
// }
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
cameraDestroy: cameraDestroy,
|
cameraDestroy: cameraDestroy,
|
||||||
createSnapshot: createSnapshot,
|
createSnapshot: createSnapshot,
|
||||||
|
|
@ -369,5 +482,9 @@ module.exports = (s,config,lang) => {
|
||||||
spawnSubstreamProcess: spawnSubstreamProcess,
|
spawnSubstreamProcess: spawnSubstreamProcess,
|
||||||
destroySubstreamProcess: destroySubstreamProcess,
|
destroySubstreamProcess: destroySubstreamProcess,
|
||||||
attachStreamChannelHandlers: attachStreamChannelHandlers,
|
attachStreamChannelHandlers: attachStreamChannelHandlers,
|
||||||
|
setActiveViewer: setActiveViewer,
|
||||||
|
getActiveViewerCount: getActiveViewerCount,
|
||||||
|
setTimedActiveViewerForHttp: setTimedActiveViewerForHttp,
|
||||||
|
attachMainProcessHandlers: attachMainProcessHandlers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,412 @@
|
||||||
|
var fs = require('fs');
|
||||||
|
const {
|
||||||
|
template,
|
||||||
|
checkEmail,
|
||||||
|
} = require("./emailUtils.js")
|
||||||
|
module.exports = function (s, config, lang, getSnapshot) {
|
||||||
|
const { getEventBasedRecordingUponCompletion } = require('../events/utils.js')(s, config, lang);
|
||||||
|
const nodeMailer = require('nodemailer');
|
||||||
|
try {
|
||||||
|
const sendMessage = async function (sendBody, files, groupKey) {
|
||||||
|
const transporter = s.group[groupKey].emailClient;
|
||||||
|
if (!transporter) {
|
||||||
|
s.userLog(
|
||||||
|
{ ke: groupKey, mid: '$USER' },
|
||||||
|
{
|
||||||
|
type: lang.NotifyErrorText,
|
||||||
|
msg: {
|
||||||
|
msg: lang.AppNotEnabledText,
|
||||||
|
app: lang.Email
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const appOptions = s.group[groupKey].emailClientOptions.transport;
|
||||||
|
const sendTo = appOptions.sendTo;
|
||||||
|
const sendData = {
|
||||||
|
from: `"shinobi.video" <${transportOptions.auth.user}>`,
|
||||||
|
to: sendTo,
|
||||||
|
subject: sendBody.subject,
|
||||||
|
html: sendBody.html,
|
||||||
|
};
|
||||||
|
if (files.length > 0) {
|
||||||
|
// sadly pushover allows only ONE single attachment
|
||||||
|
msg.file = {
|
||||||
|
name: files[0].name,
|
||||||
|
data: files[0].attachment,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
transporter.sendMail(sendData, function (err, result) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
s.userLog(result);
|
||||||
|
s.debugLog(result);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
s.userLog(
|
||||||
|
{ ke: groupKey, mid: '$USER' },
|
||||||
|
{ type: lang.NotifyErrorText, msg: err }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadAppForUser = function (user) {
|
||||||
|
const userDetails = s.parseJSON(user.details);
|
||||||
|
const optionsHost = userDetails.emailClient_host
|
||||||
|
const optionsUser = userDetails.emailClient_user
|
||||||
|
const optionsSendTo = userDetails.emailClient_sendTo || ''
|
||||||
|
if (
|
||||||
|
!s.group[user.ke].emailClient &&
|
||||||
|
userDetails.emailClient === '1' &&
|
||||||
|
optionsHost &&
|
||||||
|
optionsUser &&
|
||||||
|
optionsSendTo
|
||||||
|
){
|
||||||
|
const optionsPass = userDetails.emailClient_pass || ''
|
||||||
|
const optionsSecure = userDetails.emailClient_secure === '1' ? true : false
|
||||||
|
const optionsPort = isNaN(userDetails.emailClient_port) ? (optionsSecure ? 465 : 587) : parseInt(userDetails.emailClient_port)
|
||||||
|
const clientOptions = {
|
||||||
|
host: optionsHost,
|
||||||
|
port: optionsPort,
|
||||||
|
secure: optionsSecure,
|
||||||
|
auth: {
|
||||||
|
user: optionsUser,
|
||||||
|
pass: optionsPass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.group[user.ke].emailClientOptions = {
|
||||||
|
transport: clientOptions,
|
||||||
|
sendTo: optionsSendTo,
|
||||||
|
}
|
||||||
|
s.group[user.ke].emailClient = nodeMailer.createTransport(clientOptions)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const unloadAppForUser = function (user) {
|
||||||
|
if (
|
||||||
|
s.group[user.ke].emailClient &&
|
||||||
|
s.group[user.ke].emailClient.close
|
||||||
|
) {
|
||||||
|
s.group[user.ke].emailClient.close();
|
||||||
|
}
|
||||||
|
delete s.group[user.ke].emailClient;
|
||||||
|
delete s.group[user.ke].emailClientOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTwoFactorAuthCodeNotificationForApp = function (r) {
|
||||||
|
// r = user
|
||||||
|
if (r.details.factor_emailClient === '1') {
|
||||||
|
sendMessage({
|
||||||
|
subject: r.lang['2-Factor Authentication'],
|
||||||
|
html: template.createFramework({
|
||||||
|
title: r.lang['2-Factor Authentication'],
|
||||||
|
subtitle: r.lang['Enter this code to proceed'],
|
||||||
|
body: '<b style="font-size: 20pt;">'+s.factorAuth[r.ke][r.uid].key+'</b><br><br>'+r.lang.FactorAuthText1,
|
||||||
|
}),
|
||||||
|
},[],r.ke);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEventTriggerForApp = async (d, filter) => {
|
||||||
|
const monitorConfig = s.group[d.ke].rawMonitorConfigurations[d.id];
|
||||||
|
// d = event object
|
||||||
|
if (
|
||||||
|
s.group[d.ke].emailClient &&
|
||||||
|
(filter.emailClient || monitorConfig.details.notify_emailClient === '1') &&
|
||||||
|
!s.group[d.ke].activeMonitors[d.id].detector_emailClient
|
||||||
|
) {
|
||||||
|
var detector_emailClient_timeout;
|
||||||
|
if (
|
||||||
|
!monitorConfig.details.detector_emailClient_timeout ||
|
||||||
|
monitorConfig.details.detector_emailClient_timeout === ''
|
||||||
|
) {
|
||||||
|
detector_emailClient_timeout = 1000 * 60 * 10;
|
||||||
|
} else {
|
||||||
|
detector_emailClient_timeout =
|
||||||
|
parseFloat(
|
||||||
|
monitorConfig.details.detector_emailClient_timeout
|
||||||
|
) *
|
||||||
|
1000 *
|
||||||
|
60;
|
||||||
|
}
|
||||||
|
s.group[d.ke].activeMonitors[d.id].detector_emailClient = setTimeout(function () {
|
||||||
|
s.group[d.ke].activeMonitors[d.id].detector_emailClient = null;
|
||||||
|
}, detector_emailClient_timeout);
|
||||||
|
|
||||||
|
// lock passed
|
||||||
|
const sendMail = function(files){
|
||||||
|
const infoRows = []
|
||||||
|
Object.keys(d.details).forEach(function(key){
|
||||||
|
var value = d.details[key]
|
||||||
|
var text = value
|
||||||
|
if(value instanceof Object){
|
||||||
|
text = JSON.stringify(value,null,3)
|
||||||
|
}
|
||||||
|
infoRows.push(template.createRow({
|
||||||
|
title: key,
|
||||||
|
text: text
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
sendMessage({
|
||||||
|
subject: lang.Event+' - '+d.screenshotName,
|
||||||
|
html: template.createFramework({
|
||||||
|
title: lang.EventText1 + ' ' + d.currentTimestamp,
|
||||||
|
subtitle: lang.Event,
|
||||||
|
body: infoRows.join(''),
|
||||||
|
}),
|
||||||
|
},files || [],r.ke)
|
||||||
|
}
|
||||||
|
if(monitorConfig.details.detector_mail_send_video === '1'){
|
||||||
|
let videoPath = null
|
||||||
|
let videoName = null
|
||||||
|
const eventBasedRecording = await getEventBasedRecordingUponCompletion({
|
||||||
|
ke: d.ke,
|
||||||
|
mid: d.mid
|
||||||
|
})
|
||||||
|
if(eventBasedRecording.filePath){
|
||||||
|
videoPath = eventBasedRecording.filePath
|
||||||
|
videoName = eventBasedRecording.filename
|
||||||
|
}else{
|
||||||
|
const siftedVideoFileFromRam = await s.mergeDetectorBufferChunks(d)
|
||||||
|
videoPath = siftedVideoFileFromRam.filePath
|
||||||
|
videoName = siftedVideoFileFromRam.filename
|
||||||
|
}
|
||||||
|
if(videoPath){
|
||||||
|
fs.readFile(mergedFilepath,function(err,buffer){
|
||||||
|
if(buffer){
|
||||||
|
sendMail([
|
||||||
|
{
|
||||||
|
filename: videoName,
|
||||||
|
content: buffer
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await getSnapshot(d,monitorConfig)
|
||||||
|
sendMail([
|
||||||
|
{
|
||||||
|
filename: d.screenshotName + '.jpg',
|
||||||
|
content: d.screenshotBuffer
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEventTriggerBeforeFilterForApp = function (d, filter) {
|
||||||
|
filter.emailClient = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDetectorNoTriggerTimeoutForApp = function (e) {
|
||||||
|
//e = monitor object
|
||||||
|
var currentTime = new Date();
|
||||||
|
if (e.details.detector_notrigger_emailClient === '1') {
|
||||||
|
var html =
|
||||||
|
'*' +
|
||||||
|
lang.NoMotionEmailText2 +
|
||||||
|
' ' +
|
||||||
|
(e.details.detector_notrigger_timeout || 10) +
|
||||||
|
' ' +
|
||||||
|
lang.minutes +
|
||||||
|
'.*\n';
|
||||||
|
html +=
|
||||||
|
'**' + lang['Monitor Name'] + '** : ' + e.name + '\n';
|
||||||
|
html += '**' + lang['Monitor ID'] + '** : ' + e.id + '\n';
|
||||||
|
html += currentTime;
|
||||||
|
sendMessage({
|
||||||
|
subject: lang['"No Motion" Detector'],
|
||||||
|
html: template.createFramework({
|
||||||
|
title: lang['"No Motion" Detector'],
|
||||||
|
subtitle: 'Shinobi Event',
|
||||||
|
body: html,
|
||||||
|
}),
|
||||||
|
},[],e.ke);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMonitorUnexpectedExitForApp = (monitorConfig) => {
|
||||||
|
if (
|
||||||
|
monitorConfig.details.notify_emailClient === '1' &&
|
||||||
|
monitorConfig.details.notify_onUnexpectedExit === '1'
|
||||||
|
){
|
||||||
|
const ffmpegCommand = s.group[monitorConfig.ke].activeMonitors[monitorConfig.mid].ffmpeg
|
||||||
|
const subject = lang['Process Unexpected Exit'] + ' : ' + monitorConfig.name
|
||||||
|
const currentTime = new Date();
|
||||||
|
sendMessage({
|
||||||
|
subject: subject,
|
||||||
|
html: template.createFramework({
|
||||||
|
title: subject,
|
||||||
|
subtitle: lang['Process Crashed for Monitor'],
|
||||||
|
body: ffmpegCommand,
|
||||||
|
footerText: currentTime
|
||||||
|
}),
|
||||||
|
},[],monitorConfig.ke);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
s.loadGroupAppExtender(loadAppForUser);
|
||||||
|
s.unloadGroupAppExtender(unloadAppForUser);
|
||||||
|
s.onTwoFactorAuthCodeNotification(onTwoFactorAuthCodeNotificationForApp);
|
||||||
|
s.onEventTrigger(onEventTriggerForApp);
|
||||||
|
s.onEventTriggerBeforeFilter(onEventTriggerBeforeFilterForApp);
|
||||||
|
s.onDetectorNoTriggerTimeout(onDetectorNoTriggerTimeoutForApp);
|
||||||
|
s.onMonitorUnexpectedExit(onMonitorUnexpectedExitForApp);
|
||||||
|
s.definitions['Monitor Settings'].blocks[
|
||||||
|
'Notifications'
|
||||||
|
].info[0].info.push({
|
||||||
|
name: 'detail=notify_emailClient',
|
||||||
|
field: lang.Email,
|
||||||
|
description: '',
|
||||||
|
default: '0',
|
||||||
|
example: '',
|
||||||
|
selector: 'h_det_emailClient',
|
||||||
|
fieldType: 'select',
|
||||||
|
possible: [
|
||||||
|
{
|
||||||
|
name: lang.No,
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: lang.Yes,
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
s.definitions['Monitor Settings'].blocks['Notifications'].info.push(
|
||||||
|
{
|
||||||
|
evaluation: "$user.details.use_emailClient !== '0'",
|
||||||
|
isFormGroupGroup: true,
|
||||||
|
name: lang.Email,
|
||||||
|
color: 'blue',
|
||||||
|
'section-class': 'h_det_emailClient_input h_det_emailClient_1',
|
||||||
|
info: [
|
||||||
|
{
|
||||||
|
name: 'detail=detector_emailClient_timeout',
|
||||||
|
field: `${lang['Allow Next Alert']} (${lang['on Event']})`,
|
||||||
|
default: '10',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
s.definitions['Account Settings'].blocks[
|
||||||
|
'2-Factor Authentication'
|
||||||
|
].info.push({
|
||||||
|
name: 'detail=factor_emailClient',
|
||||||
|
field: lang.Email,
|
||||||
|
default: '1',
|
||||||
|
example: '',
|
||||||
|
fieldType: 'select',
|
||||||
|
possible: [
|
||||||
|
{
|
||||||
|
name: lang.No,
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: lang.Yes,
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
s.definitions['Account Settings'].blocks['Email'] = {
|
||||||
|
evaluation: "$user.details.use_emailClient !== '0'",
|
||||||
|
field: lang.Email,
|
||||||
|
color: 'blue',
|
||||||
|
info: [
|
||||||
|
{
|
||||||
|
name: 'detail=emailClient',
|
||||||
|
selector: 'u_emailClient',
|
||||||
|
field: lang.Enabled,
|
||||||
|
default: '0',
|
||||||
|
example: '',
|
||||||
|
fieldType: 'select',
|
||||||
|
possible: [
|
||||||
|
{
|
||||||
|
name: lang.No,
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: lang.Yes,
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hidden: true,
|
||||||
|
field: lang.Host,
|
||||||
|
name: 'detail=emailClient_host',
|
||||||
|
example: 'smtp.gmail.com',
|
||||||
|
'form-group-class': 'u_emailClient_input u_emailClient_1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hidden: true,
|
||||||
|
field: lang.Port,
|
||||||
|
name: 'detail=emailClient_port',
|
||||||
|
example: '587',
|
||||||
|
'form-group-class': 'u_emailClient_input u_emailClient_1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'detail=emailClient_secure',
|
||||||
|
field: lang.Secure,
|
||||||
|
default: '0',
|
||||||
|
example: '',
|
||||||
|
fieldType: 'select',
|
||||||
|
possible: [
|
||||||
|
{
|
||||||
|
name: lang.No,
|
||||||
|
value: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: lang.Yes,
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hidden: true,
|
||||||
|
field: lang.Email,
|
||||||
|
name: 'detail=emailClient_user',
|
||||||
|
example: 'test@gmail.com',
|
||||||
|
'form-group-class': 'u_emailClient_input u_emailClient_1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hidden: true,
|
||||||
|
field: lang.Password,
|
||||||
|
name: 'detail=emailClient_pass',
|
||||||
|
'form-group-class': 'u_emailClient_input u_emailClient_1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hidden: true,
|
||||||
|
field: lang['Send to'],
|
||||||
|
name: 'detail=emailClient_sendTo',
|
||||||
|
'form-group-class': 'u_emailClient_input u_emailClient_1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
s.definitions["Event Filters"].blocks["Action for Selected"].info.push({
|
||||||
|
"name": "actions=emailClient",
|
||||||
|
"field": lang['Email'],
|
||||||
|
"fieldType": "select",
|
||||||
|
"form-group-class": "actions-row",
|
||||||
|
"default": "",
|
||||||
|
"example": "1",
|
||||||
|
"possible": [
|
||||||
|
{
|
||||||
|
"name": lang['Original Choice'],
|
||||||
|
"value": "",
|
||||||
|
"selected": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": lang.Yes,
|
||||||
|
"value": "1",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
console.log('Could not engage Email notifications.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -17,6 +17,10 @@ const {
|
||||||
} = require('./onvifDeviceManager/utils.js')
|
} = require('./onvifDeviceManager/utils.js')
|
||||||
|
|
||||||
module.exports = function(s,config,lang,app,io){
|
module.exports = function(s,config,lang,app,io){
|
||||||
|
async function getOnvifDevice(groupKey,monitorId){
|
||||||
|
const onvifDevice = s.group[groupKey].activeMonitors[monitorId].onvifConnection || (await s.createOnvifDevice({id: monitorId, ke: groupKey})).device
|
||||||
|
return onvifDevice
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* API : Get ONVIF Data from Camera
|
* API : Get ONVIF Data from Camera
|
||||||
*/
|
*/
|
||||||
|
|
@ -26,7 +30,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
try{
|
try{
|
||||||
const groupKey = req.params.ke
|
const groupKey = req.params.ke
|
||||||
const monitorId = req.params.id
|
const monitorId = req.params.id
|
||||||
const onvifDevice = s.group[groupKey].activeMonitors[monitorId].onvifConnection
|
const onvifDevice = await getOnvifDevice(groupKey,monitorId)
|
||||||
const cameraInfo = await getUIFieldValues(onvifDevice)
|
const cameraInfo = await getUIFieldValues(onvifDevice)
|
||||||
endData.onvifData = cameraInfo
|
endData.onvifData = cameraInfo
|
||||||
}catch(err){
|
}catch(err){
|
||||||
|
|
@ -47,7 +51,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
try{
|
try{
|
||||||
const groupKey = req.params.ke
|
const groupKey = req.params.ke
|
||||||
const monitorId = req.params.id
|
const monitorId = req.params.id
|
||||||
const onvifDevice = s.group[groupKey].activeMonitors[monitorId].onvifConnection
|
const onvifDevice = await getOnvifDevice(groupKey,monitorId)
|
||||||
const form = s.getPostData(req)
|
const form = s.getPostData(req)
|
||||||
const videoToken = form.VideoConfiguration && form.VideoConfiguration.videoToken ? form.VideoConfiguration.videoToken : null
|
const videoToken = form.VideoConfiguration && form.VideoConfiguration.videoToken ? form.VideoConfiguration.videoToken : null
|
||||||
if(form.DateandTime){
|
if(form.DateandTime){
|
||||||
|
|
@ -100,7 +104,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
try{
|
try{
|
||||||
const groupKey = req.params.ke
|
const groupKey = req.params.ke
|
||||||
const monitorId = req.params.id
|
const monitorId = req.params.id
|
||||||
const onvifDevice = s.group[groupKey].activeMonitors[monitorId].onvifConnection
|
const onvifDevice = await getOnvifDevice(groupKey,monitorId)
|
||||||
const cameraInfo = await rebootCamera(onvifDevice)
|
const cameraInfo = await rebootCamera(onvifDevice)
|
||||||
endData.onvifData = cameraInfo
|
endData.onvifData = cameraInfo
|
||||||
}catch(err){
|
}catch(err){
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,7 @@ const getDeviceInformation = async (onvifDevice,options) => {
|
||||||
response.ok = false
|
response.ok = false
|
||||||
response.error = err.stack.toString().toString()
|
response.error = err.stack.toString().toString()
|
||||||
s.debugLog(err)
|
s.debugLog(err)
|
||||||
|
s.debugLog(onvifDevice)
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,9 @@ module.exports = (s,config,lang) => {
|
||||||
ProfileToken : device.current_profile.token,
|
ProfileToken : device.current_profile.token,
|
||||||
Protocol : 'RTSP'
|
Protocol : 'RTSP'
|
||||||
})
|
})
|
||||||
|
const snapUri = (await device.services.media.getSnapshotUri({
|
||||||
|
ProfileToken : device.current_profile.token,
|
||||||
|
})).GetSnapshotUriResponse.MediaUri.Uri
|
||||||
var cameraResponse = {
|
var cameraResponse = {
|
||||||
ip: camera.ip,
|
ip: camera.ip,
|
||||||
port: camera.port,
|
port: camera.port,
|
||||||
|
|
@ -147,7 +150,7 @@ module.exports = (s,config,lang) => {
|
||||||
imageSnap = (await s.getSnapshotFromOnvif({
|
imageSnap = (await s.getSnapshotFromOnvif({
|
||||||
username: onvifUsername,
|
username: onvifUsername,
|
||||||
password: onvifPassword,
|
password: onvifPassword,
|
||||||
uri: cameraResponse.uri,
|
uri: snapUri,
|
||||||
})).toString('base64');
|
})).toString('base64');
|
||||||
}catch(err){
|
}catch(err){
|
||||||
s.debugLog(err)
|
s.debugLog(err)
|
||||||
|
|
@ -185,7 +188,7 @@ module.exports = (s,config,lang) => {
|
||||||
error: errorMessage
|
error: errorMessage
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
s.debugLog(err)
|
if(config.debugLogVerbose)s.debugLog(err);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return responseList
|
return responseList
|
||||||
|
|
|
||||||
|
|
@ -955,7 +955,22 @@ module.exports = function(s,config,lang,io){
|
||||||
s.deleteFileBinEntry(d.file)
|
s.deleteFileBinEntry(d.file)
|
||||||
break;
|
break;
|
||||||
case's.setDiskUsedForGroup':
|
case's.setDiskUsedForGroup':
|
||||||
s.setDiskUsedForGroup(d.ke,d.size,d.target || undefined)
|
function doOnMain(){
|
||||||
|
s.setDiskUsedForGroup(d.ke,d.size,d.target || undefined)
|
||||||
|
}
|
||||||
|
if(d.videoRow){
|
||||||
|
let storageIndex = s.getVideoStorageIndex(d.videoRow);
|
||||||
|
if(storageIndex){
|
||||||
|
s.setDiskUsedForGroupAddStorage(d.ke,{
|
||||||
|
size: d.size,
|
||||||
|
storageIndex: storageIndex
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
doOnMain()
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
doOnMain()
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case'start':case'end':
|
case'start':case'end':
|
||||||
d.mid='_cron';s.userLog(d,{type:'cron',msg:d.msg})
|
d.mid='_cron';s.userLog(d,{type:'cron',msg:d.msg})
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
endDate: req.query.end,
|
endDate: req.query.end,
|
||||||
startOperator: req.query.startOperator,
|
startOperator: req.query.startOperator,
|
||||||
endOperator: req.query.endOperator,
|
endOperator: req.query.endOperator,
|
||||||
|
noLimit: req.query.noLimit,
|
||||||
limit: req.query.limit,
|
limit: req.query.limit,
|
||||||
archived: req.query.archived,
|
archived: req.query.archived,
|
||||||
rowType: 'frames',
|
rowType: 'frames',
|
||||||
|
|
@ -241,9 +242,9 @@ module.exports = function(s,config,lang,app,io){
|
||||||
const frames = []
|
const frames = []
|
||||||
var n = 0
|
var n = 0
|
||||||
framesPosted.forEach((frame) => {
|
framesPosted.forEach((frame) => {
|
||||||
var firstParam = ['ke','=',req.params.ke]
|
var firstParam = [['ke','=',req.params.ke],['mid','=',req.params.id],['filename','=',frame.filename]]
|
||||||
if(n !== 0)firstParam = (['or']).concat(firstParam)
|
if(n !== 0)firstParam[0] = (['or']).concat(firstParam[0])
|
||||||
frames.push(firstParam,['mid','=',req.params.id],['filename','=',frame.filename])
|
frames.push(...firstParam)
|
||||||
++n
|
++n
|
||||||
})
|
})
|
||||||
s.knexQuery({
|
s.knexQuery({
|
||||||
|
|
@ -252,6 +253,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
table: "Timelapse Frames",
|
table: "Timelapse Frames",
|
||||||
where: frames
|
where: frames
|
||||||
},(err,r) => {
|
},(err,r) => {
|
||||||
|
s.debugLog("Timelapse Frames Building Video",r.length)
|
||||||
if(r.length === 0){
|
if(r.length === 0){
|
||||||
s.closeJsonResponse(res,{
|
s.closeJsonResponse(res,{
|
||||||
ok: false
|
ok: false
|
||||||
|
|
@ -330,6 +332,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
groupKey: req.params.ke,
|
groupKey: req.params.ke,
|
||||||
archived: req.query.archived,
|
archived: req.query.archived,
|
||||||
filename: req.params.filename,
|
filename: req.params.filename,
|
||||||
|
limit: 1,
|
||||||
rowType: 'frames',
|
rowType: 'frames',
|
||||||
endIsStartTo: true
|
endIsStartTo: true
|
||||||
},(response) => {
|
},(response) => {
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ module.exports = function(s,config,lang){
|
||||||
filename: filename,
|
filename: filename,
|
||||||
mid: e.id,
|
mid: e.id,
|
||||||
ke: e.ke,
|
ke: e.ke,
|
||||||
time: s.nameToTime(filename),
|
time: new Date(s.nameToTime(filename)),
|
||||||
end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
|
end: s.formattedTime(new Date,'YYYY-MM-DD HH:mm:ss')
|
||||||
},'GRP_'+e.ke);
|
},'GRP_'+e.ke);
|
||||||
var storageIndex = s.getVideoStorageIndex(e)
|
var storageIndex = s.getVideoStorageIndex(e)
|
||||||
|
|
@ -471,13 +471,13 @@ module.exports = function(s,config,lang){
|
||||||
file.pipe(res)
|
file.pipe(res)
|
||||||
return file
|
return file
|
||||||
}
|
}
|
||||||
s.createVideoFromTimelapse = function(timelapseFrames,framesPerSecond,callback){
|
s.createVideoFromTimelapse = async function(timelapseFrames,framesPerSecond,callback){
|
||||||
framesPerSecond = parseInt(framesPerSecond)
|
framesPerSecond = parseInt(framesPerSecond)
|
||||||
if(!framesPerSecond || isNaN(framesPerSecond))framesPerSecond = 2
|
if(!framesPerSecond || isNaN(framesPerSecond))framesPerSecond = 2
|
||||||
var frames = timelapseFrames.reverse()
|
var frames = timelapseFrames.reverse()
|
||||||
var ke = frames[0].ke
|
var ke = frames[0].ke
|
||||||
var mid = frames[0].mid
|
var mid = frames[0].mid
|
||||||
var finalFileName = frames[0].filename.split('.')[0] + '_' + frames[frames.length - 1].filename.split('.')[0] + `-${framesPerSecond}fps`
|
var finalFileName = `${s.md5(JSON.stringify(frames))}-${framesPerSecond}fps`
|
||||||
var concatFiles = []
|
var concatFiles = []
|
||||||
var createLocation
|
var createLocation
|
||||||
frames.forEach(function(frame,frameNumber){
|
frames.forEach(function(frame,frameNumber){
|
||||||
|
|
@ -515,7 +515,7 @@ module.exports = function(s,config,lang){
|
||||||
}
|
}
|
||||||
},4000)
|
},4000)
|
||||||
})
|
})
|
||||||
videoBuildProcess.on('exit',function(data){
|
videoBuildProcess.on('close',function(data){
|
||||||
var timeNow = new Date()
|
var timeNow = new Date()
|
||||||
var fileStats = fs.statSync(finalMp4OutputLocation)
|
var fileStats = fs.statSync(finalMp4OutputLocation)
|
||||||
var details = {}
|
var details = {}
|
||||||
|
|
@ -547,12 +547,11 @@ module.exports = function(s,config,lang){
|
||||||
if(!err)videoBuildProcess.stdin.write(buffer)
|
if(!err)videoBuildProcess.stdin.write(buffer)
|
||||||
if(currentFile === concatFiles.length - 1){
|
if(currentFile === concatFiles.length - 1){
|
||||||
//is last
|
//is last
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
setTimeout(function(){
|
setTimeout(async function(){
|
||||||
++currentFile
|
++currentFile
|
||||||
readFile()
|
readFile()
|
||||||
},1/framesPerSecond)
|
},10/framesPerSecond)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -565,13 +564,22 @@ module.exports = function(s,config,lang){
|
||||||
msg: lang['Started Building']
|
msg: lang['Started Building']
|
||||||
})
|
})
|
||||||
}else{
|
}else{
|
||||||
callback({
|
if(s.group[ke].activeMonitors[mid].buildingTimelapseVideo){
|
||||||
ok: false,
|
callback({
|
||||||
fileExists: true,
|
ok: false,
|
||||||
filename: finalFileName + '.mp4',
|
fileExists: false,
|
||||||
fileLocation: finalMp4OutputLocation,
|
fileLocation: finalMp4OutputLocation,
|
||||||
msg: lang['Already exists']
|
msg: lang.Building
|
||||||
})
|
})
|
||||||
|
}else{
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
fileExists: true,
|
||||||
|
filename: finalFileName + '.mp4',
|
||||||
|
fileLocation: finalMp4OutputLocation,
|
||||||
|
msg: lang['Already exists']
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
callback({
|
callback({
|
||||||
|
|
@ -591,9 +599,11 @@ module.exports = function(s,config,lang){
|
||||||
}
|
}
|
||||||
s.getVideoStorageIndex = function(video){
|
s.getVideoStorageIndex = function(video){
|
||||||
try{
|
try{
|
||||||
var details = s.parseJSON(video.details) || {}
|
const monitorId = video.id || video.mid
|
||||||
var storageId = details.storageId
|
const details = s.parseJSON(video.details) || {}
|
||||||
if(s.group[video.ke] && s.group[video.ke].activeMonitors[video.id] && s.group[video.ke].activeMonitors[video.id].addStorageId)storageId = s.group[video.ke].activeMonitors[video.id].addStorageId
|
let storageId = details.storageId
|
||||||
|
const activeMonitor = s.group[video.ke] && s.group[video.ke].activeMonitors[monitorId] ? s.group[video.ke].activeMonitors[monitorId] : null;
|
||||||
|
if(activeMonitor && activeMonitor.addStorageId)storageId = activeMonitor.addStorageId;
|
||||||
if(storageId){
|
if(storageId){
|
||||||
return s.group[video.ke].addStorageUse[storageId]
|
return s.group[video.ke].addStorageUse[storageId]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ module.exports = function(s,config,lang,app){
|
||||||
*/
|
*/
|
||||||
app.all(config.webPaths.adminApiPrefix+':auth/accounts/:ke/delete', function (req,res){
|
app.all(config.webPaths.adminApiPrefix+':auth/accounts/:ke/delete', function (req,res){
|
||||||
s.auth(req.params,function(user){
|
s.auth(req.params,function(user){
|
||||||
|
const groupKey = req.params.ke;
|
||||||
var endData = {
|
var endData = {
|
||||||
ok : false
|
ok : false
|
||||||
}
|
}
|
||||||
|
|
@ -106,47 +107,60 @@ module.exports = function(s,config,lang,app){
|
||||||
}
|
}
|
||||||
var form = s.getPostData(req) || {}
|
var form = s.getPostData(req) || {}
|
||||||
var uid = form.uid || s.getPostData(req,'uid',false)
|
var uid = form.uid || s.getPostData(req,'uid',false)
|
||||||
var mail = form.mail || s.getPostData(req,'mail',false)
|
|
||||||
s.knexQuery({
|
|
||||||
action: "delete",
|
|
||||||
table: "Users",
|
|
||||||
where: {
|
|
||||||
ke: req.params.ke,
|
|
||||||
uid: uid,
|
|
||||||
mail: mail,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
s.knexQuery({
|
s.knexQuery({
|
||||||
action: "select",
|
action: "select",
|
||||||
columns: "*",
|
columns: "*",
|
||||||
table: "API",
|
table: "Users",
|
||||||
where: [
|
where: [
|
||||||
['ke','=',req.params.ke],
|
['ke','=',groupKey],
|
||||||
['uid','=',uid],
|
['uid','=',uid],
|
||||||
]
|
]
|
||||||
},function(err,rows){
|
},function(err,usersFound){
|
||||||
if(rows && rows[0]){
|
const theUserUpForDeletion = usersFound[0]
|
||||||
rows.forEach(function(row){
|
if(theUserUpForDeletion){
|
||||||
delete(s.api[row.code])
|
|
||||||
})
|
|
||||||
s.knexQuery({
|
s.knexQuery({
|
||||||
action: "delete",
|
action: "delete",
|
||||||
table: "API",
|
table: "Users",
|
||||||
where: {
|
where: {
|
||||||
ke: req.params.ke,
|
ke: groupKey,
|
||||||
uid: uid,
|
uid: uid,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
s.knexQuery({
|
||||||
|
action: "select",
|
||||||
|
columns: "*",
|
||||||
|
table: "API",
|
||||||
|
where: [
|
||||||
|
['ke','=',groupKey],
|
||||||
|
['uid','=',uid],
|
||||||
|
]
|
||||||
|
},function(err,rows){
|
||||||
|
if(rows && rows[0]){
|
||||||
|
rows.forEach(function(row){
|
||||||
|
delete(s.api[row.code])
|
||||||
|
})
|
||||||
|
s.knexQuery({
|
||||||
|
action: "delete",
|
||||||
|
table: "API",
|
||||||
|
where: {
|
||||||
|
ke: groupKey,
|
||||||
|
uid: uid,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
s.tx({
|
||||||
|
f: 'delete_sub_account',
|
||||||
|
ke: groupKey,
|
||||||
|
uid: uid,
|
||||||
|
mail: theUserUpForDeletion.mail
|
||||||
|
},'ADM_'+groupKey)
|
||||||
|
endData.ok = true
|
||||||
|
}else{
|
||||||
|
endData.msg = user.lang['User Not Found']
|
||||||
}
|
}
|
||||||
|
s.closeJsonResponse(res,endData)
|
||||||
})
|
})
|
||||||
s.tx({
|
|
||||||
f: 'delete_sub_account',
|
|
||||||
ke: req.params.ke,
|
|
||||||
uid: uid,
|
|
||||||
mail: mail
|
|
||||||
},'ADM_'+req.params.ke)
|
|
||||||
endData.ok = true
|
|
||||||
s.closeJsonResponse(res,endData)
|
|
||||||
},res,req)
|
},res,req)
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -839,13 +839,20 @@ module.exports = function(s,config,lang,app,io){
|
||||||
}else{
|
}else{
|
||||||
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
|
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
|
||||||
const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
|
const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
|
||||||
if(!activeMonitor.subStreamProcess){
|
const substreamConfig = monitorConfig.details.substream
|
||||||
response.ok = true
|
if(
|
||||||
activeMonitor.allowDestroySubstream = false;
|
substreamConfig.output
|
||||||
spawnSubstreamProcess(monitorConfig)
|
){
|
||||||
|
if(!activeMonitor.subStreamProcess){
|
||||||
|
response.ok = true
|
||||||
|
activeMonitor.allowDestroySubstream = false;
|
||||||
|
spawnSubstreamProcess(monitorConfig)
|
||||||
|
}else{
|
||||||
|
activeMonitor.allowDestroySubstream = true
|
||||||
|
await destroySubstreamProcess(activeMonitor)
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
activeMonitor.allowDestroySubstream = true
|
response.msg = lang['Invalid Settings']
|
||||||
await destroySubstreamProcess(activeMonitor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.closeJsonResponse(res,response);
|
s.closeJsonResponse(res,response);
|
||||||
|
|
@ -942,6 +949,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
endTime: req.query.end,
|
endTime: req.query.end,
|
||||||
startTimeOperator: req.query.startOperator,
|
startTimeOperator: req.query.startOperator,
|
||||||
endTimeOperator: req.query.endOperator,
|
endTimeOperator: req.query.endOperator,
|
||||||
|
noLimit: req.query.noLimit,
|
||||||
limit: req.query.limit,
|
limit: req.query.limit,
|
||||||
archived: req.query.archived,
|
archived: req.query.archived,
|
||||||
endIsStartTo: !!req.query.endIsStartTo,
|
endIsStartTo: !!req.query.endIsStartTo,
|
||||||
|
|
@ -1014,6 +1022,7 @@ module.exports = function(s,config,lang,app,io){
|
||||||
endTime: req.query.end,
|
endTime: req.query.end,
|
||||||
startTimeOperator: req.query.startOperator,
|
startTimeOperator: req.query.startOperator,
|
||||||
endTimeOperator: req.query.endOperator,
|
endTimeOperator: req.query.endOperator,
|
||||||
|
noLimit: req.query.noLimit,
|
||||||
limit: req.query.limit,
|
limit: req.query.limit,
|
||||||
endIsStartTo: true,
|
endIsStartTo: true,
|
||||||
parseRowDetails: true,
|
parseRowDetails: true,
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
|
@ -35,13 +35,14 @@
|
||||||
"mp4frag": "^0.2.0",
|
"mp4frag": "^0.2.0",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"node-fetch": "3.0.0-beta.9",
|
"node-fetch": "3.0.0-beta.9",
|
||||||
|
"node-pushover": "^1.0.0",
|
||||||
"node-ssh": "^11.1.1",
|
"node-ssh": "^11.1.1",
|
||||||
"node-telegram-bot-api": "^0.52.0",
|
"node-telegram-bot-api": "^0.52.0",
|
||||||
"nodemailer": "^6.4.11",
|
"nodemailer": "^6.4.11",
|
||||||
"node-pushover": "^1.0.0",
|
"pam-diff": "^1.1.0",
|
||||||
"pam-diff": "^1.0.0",
|
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pipe2pam": "^0.6.2",
|
"pipe2pam": "^0.6.2",
|
||||||
|
"pixel-change": "^1.1.0",
|
||||||
"request": "^2.88.0",
|
"request": "^2.88.0",
|
||||||
"sat": "^0.7.1",
|
"sat": "^0.7.1",
|
||||||
"shinobi-onvif": "0.1.9",
|
"shinobi-onvif": "0.1.9",
|
||||||
|
|
@ -69,9 +70,9 @@
|
||||||
"node12"
|
"node12"
|
||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"libs/cameraThread/detector.js",
|
"libs/cameraThread/detector.js",
|
||||||
"libs/cameraThread/singleCamera.js",
|
"libs/cameraThread/singleCamera.js",
|
||||||
"libs/cameraThread/snapshot.js"
|
"libs/cameraThread/snapshot.js"
|
||||||
],
|
],
|
||||||
"assets": [
|
"assets": [
|
||||||
"definitions/**/*",
|
"definitions/**/*",
|
||||||
|
|
|
||||||
|
|
@ -179,3 +179,7 @@
|
||||||
#powerVideo .vis-item.vis-box {
|
#powerVideo .vis-item.vis-box {
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#powerVideo .vis-labelset .vis-label {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#tab-timelapseViewer .frameIcons{
|
#tab-timelapseViewer .frameIcons{
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
max-height: 400px;
|
||||||
}
|
}
|
||||||
#tab-timelapseViewer .frameIcons .frame{
|
#tab-timelapseViewer .frameIcons .frame{
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
|
|
||||||
|
|
@ -29,3 +29,40 @@
|
||||||
.tab-videoPlayer:hover .tab-videoPlayer-event-objects {
|
.tab-videoPlayer:hover .tab-videoPlayer-event-objects {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* video-time-strip */
|
||||||
|
.video-time-img {
|
||||||
|
background-color: #000;
|
||||||
|
background-size: contain;
|
||||||
|
background-position: center;
|
||||||
|
min-height: 400px;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
.video-time-img:not(.video-time-no-img) .card-body:hover {
|
||||||
|
background: rgba(0,0,0,0.9);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.video-time-strip {
|
||||||
|
min-height: 30px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.video-time-needle {
|
||||||
|
position: absolute;
|
||||||
|
border-left: 3px solid #1f80f9;
|
||||||
|
height: 100%;
|
||||||
|
transition: none;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.video-time-needle-event {
|
||||||
|
border-left: 3px solid #f9f21f;
|
||||||
|
}
|
||||||
|
.video-day-slice {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.video-time-header {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(0,0,0,0.6)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -383,3 +383,36 @@ ul.squeeze {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Shake Animation */
|
||||||
|
.animate-shake {
|
||||||
|
animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both;
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
backface-visibility: hidden;
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-shake-hover:hover {
|
||||||
|
animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both;
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
backface-visibility: hidden;
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shake {
|
||||||
|
10%, 90% {
|
||||||
|
transform: translate3d(-1px, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
20%, 80% {
|
||||||
|
transform: translate3d(2px, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
30%, 50%, 70% {
|
||||||
|
transform: translate3d(-4px, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40%, 60% {
|
||||||
|
transform: translate3d(4px, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ $(document).ready(function(e){
|
||||||
detailsElement.val(JSON.stringify(details))
|
detailsElement.val(JSON.stringify(details))
|
||||||
}
|
}
|
||||||
var getApiKeys = function(callback){
|
var getApiKeys = function(callback){
|
||||||
$.get(getApiPrefix('api') + '/list',function(data){
|
$.getJSON(getApiPrefix('api') + '/list',function(data){
|
||||||
callback(data.keys)
|
callback(data.keys)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ $(document).ready(function(e){
|
||||||
var form = el.serializeObject()
|
var form = el.serializeObject()
|
||||||
var flags = 'default'
|
var flags = 'default'
|
||||||
var url = form.url.trim()
|
var url = form.url.trim()
|
||||||
$.get(`${getApiPrefix()}/probe/${$user.ke}?url=${url}`,function(data){
|
$.getJSON(`${getApiPrefix()}/probe/${$user.ke}?url=${url}`,function(data){
|
||||||
if(data.ok === true){
|
if(data.ok === true){
|
||||||
var html
|
var html
|
||||||
try{
|
try{
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ $(document).ready(function(){
|
||||||
}
|
}
|
||||||
var getConfigurationsFromHub = function(rowLimit,skipOver,explore,searchQuery,sortBy,sortDirection,callback){
|
var getConfigurationsFromHub = function(rowLimit,skipOver,explore,searchQuery,sortBy,sortDirection,callback){
|
||||||
// $.get(,callback)
|
// $.get(,callback)
|
||||||
$.get(`https://hub.shinobi.video/searchConfiguration?skipOver=${skipOver}&rowLimit=${rowLimit}&sortBy=${sortBy}&sortDirection=${sortDirection}${searchQuery ? `&text=${searchQuery}` : ''}`,function(data){
|
$.getJSON(`https://hub.shinobi.video/searchConfiguration?skipOver=${skipOver}&rowLimit=${rowLimit}&sortBy=${sortBy}&sortDirection=${sortDirection}${searchQuery ? `&text=${searchQuery}` : ''}`,function(data){
|
||||||
callback(data)
|
callback(data)
|
||||||
// $.get(getApiPrefix() + `/getShinobiHubConfigurations/${$user.ke}/cam?rowLimit=${rowLimit}&skipOver=${skipOver}&explore=${explore ? explore : "0"}&search=${searchQuery}&sortDirection=${sortDirection}&sortBy=${sortBy}`,function(privateData){
|
// $.get(getApiPrefix() + `/getShinobiHubConfigurations/${$user.ke}/cam?rowLimit=${rowLimit}&skipOver=${skipOver}&explore=${explore ? explore : "0"}&search=${searchQuery}&sortDirection=${sortDirection}&sortBy=${sortBy}`,function(privateData){
|
||||||
// callback(data.concat(privateData || []))
|
// callback(data.concat(privateData || []))
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,9 @@ function base64ArrayBuffer(arrayBuffer) {
|
||||||
|
|
||||||
return base64
|
return base64
|
||||||
}
|
}
|
||||||
|
function getLocationPathName(){
|
||||||
|
return location.pathname.endsWith('/') ? location.pathname : location.pathname
|
||||||
|
}
|
||||||
function debugLog(...args){
|
function debugLog(...args){
|
||||||
console.log(...args)
|
console.log(...args)
|
||||||
}
|
}
|
||||||
|
|
@ -198,7 +201,7 @@ function liveStamp(){
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMonitorsIntoMemory(callback){
|
function loadMonitorsIntoMemory(callback){
|
||||||
$.get(`${getApiPrefix(`monitor`)}`,function(data){
|
$.getJSON(`${getApiPrefix(`monitor`)}`,function(data){
|
||||||
$.each(data,function(n,monitor){
|
$.each(data,function(n,monitor){
|
||||||
monitor.details = safeJsonParse(monitor.details)
|
monitor.details = safeJsonParse(monitor.details)
|
||||||
loadedMonitors[monitor.mid] = monitor
|
loadedMonitors[monitor.mid] = monitor
|
||||||
|
|
@ -261,6 +264,10 @@ function blipTo(xPageValue,yPageValue){
|
||||||
|
|
||||||
function openTab(theTab,loadData,backAction,haltTrigger,type){
|
function openTab(theTab,loadData,backAction,haltTrigger,type){
|
||||||
loadData = loadData ? loadData : {}
|
loadData = loadData ? loadData : {}
|
||||||
|
if(tabTree && tabTree.back && tabTree.back.name === theTab){
|
||||||
|
goBackOneTab()
|
||||||
|
return;
|
||||||
|
}
|
||||||
saveTabBlipPosition(activeTabName)
|
saveTabBlipPosition(activeTabName)
|
||||||
var allTabs = $('.page-tab');
|
var allTabs = $('.page-tab');
|
||||||
allTabs.hide().removeClass('tab-active');
|
allTabs.hide().removeClass('tab-active');
|
||||||
|
|
@ -578,7 +585,7 @@ function diffObject(obj1, obj2) {
|
||||||
function getAllSectionsFromDefinition(definitionsBase){
|
function getAllSectionsFromDefinition(definitionsBase){
|
||||||
var sections = {}
|
var sections = {}
|
||||||
var addSection = function(section,parentName){
|
var addSection = function(section,parentName){
|
||||||
sections[section.name] = {
|
sections[section.id + section.name] = {
|
||||||
name: section.name,
|
name: section.name,
|
||||||
id: section.id,
|
id: section.id,
|
||||||
color: section.color,
|
color: section.color,
|
||||||
|
|
@ -593,7 +600,7 @@ function getAllSectionsFromDefinition(definitionsBase){
|
||||||
}
|
}
|
||||||
if(section.blocks){
|
if(section.blocks){
|
||||||
$.each(section.blocks,function(m,block){
|
$.each(section.blocks,function(m,block){
|
||||||
addSection(block)
|
addSection(block,section.name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -677,14 +684,28 @@ function drawMonitorListToSelector(jqTarget,selectFirst,showId){
|
||||||
.change()
|
.change()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var logWriterIconIndicator = $('#side-menu-link-logViewer i')
|
||||||
|
var logWriterIconIndicatorShaking = false
|
||||||
|
var logWriterIconIndicatorTimeout = null
|
||||||
|
function shakeLogWriterIcon(){
|
||||||
|
if(logWriterIconIndicatorShaking)return;
|
||||||
|
logWriterIconIndicatorShaking = true;
|
||||||
|
logWriterIconIndicator.addClass('animate-shake')
|
||||||
|
logWriterIconIndicatorTimeout = setTimeout(function(){
|
||||||
|
logWriterIconIndicatorShaking = false;
|
||||||
|
logWriterIconIndicator.removeClass('animate-shake')
|
||||||
|
},3000)
|
||||||
|
}
|
||||||
var logWriterFloodTimeout = null
|
var logWriterFloodTimeout = null
|
||||||
var logWriterFloodCounter = 0
|
var logWriterFloodCounter = 0
|
||||||
var logWriterFloodLock = null
|
var logWriterFloodLock = null
|
||||||
function buildLogRow(v){
|
function buildLogRow(v){
|
||||||
|
var monitor = loadedMonitors[v.mid]
|
||||||
|
var humanMonitorName = monitor ? monitor.name + ` (${monitor.mid}) : ` : ''
|
||||||
var html = ''
|
var html = ''
|
||||||
html += `<div class="card shadow-lg mb-3 px-0 btn-default search-row">
|
html += `<div class="log-item card shadow-lg mb-3 px-0 btn-default search-row">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<small class="${definitions.Theme.isDark ? 'text-white' : ''}">${v.info && v.info.type ? v.info.type : v.mid}</small>
|
<small class="${definitions.Theme.isDark ? 'text-white' : ''}">${humanMonitorName}${v.info && v.info.type ? v.info.type : v.mid}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div>${jsonToHtmlBlock(v.info.msg)}</div>
|
<div>${jsonToHtmlBlock(v.info.msg)}</div>
|
||||||
|
|
@ -718,10 +739,12 @@ function logWriterDraw(id,data){
|
||||||
info: data.log,
|
info: data.log,
|
||||||
time: data.time,
|
time: data.time,
|
||||||
})
|
})
|
||||||
|
shakeLogWriterIcon()
|
||||||
$(elementTags).prepend(html).each(function(n,v){
|
$(elementTags).prepend(html).each(function(n,v){
|
||||||
var el = $(v);
|
var el = $(v);
|
||||||
if(el.find('.log-item').length > 10){
|
var theRows = el.find('.log-item')
|
||||||
v.find('.log-item:last').remove()
|
if(theRows.length > 10){
|
||||||
|
theRows.last().remove()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -853,6 +876,14 @@ $(document).ready(function(){
|
||||||
deleteTab(tabName)
|
deleteTab(tabName)
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
|
.on('click','.delete-tab-dynamic',function(e){
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
var tabName = $(this).parents('.page-tab').attr('id').replace('tab-','')
|
||||||
|
goBackOneTab()
|
||||||
|
deleteTab(tabName)
|
||||||
|
return false;
|
||||||
|
})
|
||||||
.on('click','[page-open]',function(){
|
.on('click','[page-open]',function(){
|
||||||
var el = $(this)
|
var el = $(this)
|
||||||
var pageChoice = el.attr('page-open')
|
var pageChoice = el.attr('page-open')
|
||||||
|
|
|
||||||
|
|
@ -145,9 +145,9 @@ $(document).ready(function(){
|
||||||
eventEndTime = formattedTimeForFilename(options.endDate,false)
|
eventEndTime = formattedTimeForFilename(options.endDate,false)
|
||||||
requestQueries.push(`end=${eventEndTime}`)
|
requestQueries.push(`end=${eventEndTime}`)
|
||||||
}
|
}
|
||||||
$.get(`${getApiPrefix(`videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`limit=${limit}`]).join('&')}`,function(data){
|
$.getJSON(`${getApiPrefix(`videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`limit=${limit}`]).join('&')}`,function(data){
|
||||||
var videos = data.videos
|
var videos = data.videos
|
||||||
$.get(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.join('&')}`,function(eventData){
|
$.getJSON(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.join('&')}`,function(eventData){
|
||||||
var newEventList = applyVideosToEventsList(videos,eventData)
|
var newEventList = applyVideosToEventsList(videos,eventData)
|
||||||
$.each(newEventList,function(n,event){
|
$.each(newEventList,function(n,event){
|
||||||
loadedEventsInMemory[`${event.mid}${event.time}`] = event
|
loadedEventsInMemory[`${event.mid}${event.time}`] = event
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
$(document).ready(function(){
|
||||||
|
var helpWindow = $('#help_window')
|
||||||
|
var openMessage = null
|
||||||
|
function lessThanOneWeekAgo(date){
|
||||||
|
const WEEK = 1000 * 60 * 60 * 24 * 7;
|
||||||
|
const aWeekAgo = Date.now() - WEEK;
|
||||||
|
|
||||||
|
return date < aWeekAgo;
|
||||||
|
}
|
||||||
|
function showHelpNotice(){
|
||||||
|
var buttonHtml = ``
|
||||||
|
$.each([
|
||||||
|
{
|
||||||
|
icon: 'share-square-o',
|
||||||
|
color: 'default',
|
||||||
|
text: 'ShinobiShop Subscriptions',
|
||||||
|
href: 'https://licenses.shinobi.video/subscribe',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'paypal',
|
||||||
|
color: 'success',
|
||||||
|
text: 'Donate by PayPal',
|
||||||
|
href: 'https://www.paypal.me/ShinobiCCTV',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'bank',
|
||||||
|
color: 'default',
|
||||||
|
text: 'University of Zurich (UZH)',
|
||||||
|
href: 'https://www.media.uzh.ch/en/Press-Releases/2017/Generosity.html',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'cube',
|
||||||
|
color: 'danger',
|
||||||
|
text: lang[`Don't Show for 1 Week`],
|
||||||
|
href: '#',
|
||||||
|
class: 'hide_donate',
|
||||||
|
},
|
||||||
|
],function(n,button){
|
||||||
|
buttonHtml += `<a style="margin-bottom:4px" ${ button.href ? `href="${button.href}"` : '' } target="_blank" class="d-flex flex-row btn btn-sm btn-block btn-${ button.color } ${ button.class }">
|
||||||
|
<div><i class="fa fa-${ button.icon }" aria-hidden="true"></i></div>
|
||||||
|
<div class="text-center flex-grow-1">${ button.text }</div>
|
||||||
|
</a>`
|
||||||
|
})
|
||||||
|
openMessage = new PNotify({
|
||||||
|
title: `It's a proven fact`,
|
||||||
|
text: `
|
||||||
|
<div class="mb-3">Generosity makes you a happier person, please consider supporting the development.</div>
|
||||||
|
<div class="mb-3">If you are already supporting the development, please contact us or use your provided license key and we can get this popup to go away for you <i class="fa fa-smile-o"></i> Cheers!</div>
|
||||||
|
${buttonHtml}`,
|
||||||
|
hide: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function dontShowForOneWeek(){
|
||||||
|
if(openMessage){
|
||||||
|
openMessage.remove()
|
||||||
|
}
|
||||||
|
dashboardOptions('subscription_checked',new Date());
|
||||||
|
}
|
||||||
|
if(!userHasSubscribed && !dashboardOptions().subscription_checked || lessThanOneWeekAgo(new Date(dashboardOptions().subscription_checked))){
|
||||||
|
setTimeout(function(){
|
||||||
|
showHelpNotice()
|
||||||
|
},1000 * 60 * 0.2)
|
||||||
|
}
|
||||||
|
$('body').on('click','.hide_donate',function(e){
|
||||||
|
e.preventDefault()
|
||||||
|
dontShowForOneWeek()
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
console.log('Please support the Shinobi developement.')
|
||||||
|
console.log('https://licenses.shinobi.video/subscribe')
|
||||||
|
})
|
||||||
|
|
@ -159,6 +159,9 @@ function buildLiveGridBlock(monitor){
|
||||||
<div class="mdl-overlay-menu-backdrop hidden">
|
<div class="mdl-overlay-menu-backdrop hidden">
|
||||||
<ul class="mdl-overlay-menu list-group">`
|
<ul class="mdl-overlay-menu list-group">`
|
||||||
var buttons = streamBlockInfo.links
|
var buttons = streamBlockInfo.links
|
||||||
|
if(!monitor.details.control === '1'){
|
||||||
|
delete(buttons["Control"])
|
||||||
|
}
|
||||||
if(!permissionCheck('video_view',monitor.mid)){
|
if(!permissionCheck('video_view',monitor.mid)){
|
||||||
delete(buttons["Videos List"])
|
delete(buttons["Videos List"])
|
||||||
delete(buttons["Time-lapse"])
|
delete(buttons["Time-lapse"])
|
||||||
|
|
@ -273,7 +276,6 @@ function drawLiveGridBlock(monitorConfig,subStreamChannel){
|
||||||
setLiveGridOpenCount(1)
|
setLiveGridOpenCount(1)
|
||||||
}
|
}
|
||||||
initiateLiveGridPlayer(loadedMonitors[monitorId],subStreamChannel)
|
initiateLiveGridPlayer(loadedMonitors[monitorId],subStreamChannel)
|
||||||
loadVideoMiniList(monitorId)
|
|
||||||
}
|
}
|
||||||
function initiateLiveGridPlayer(monitor,subStreamChannel){
|
function initiateLiveGridPlayer(monitor,subStreamChannel){
|
||||||
var livePlayerElement = loadedLiveGrids[monitor.mid]
|
var livePlayerElement = loadedLiveGrids[monitor.mid]
|
||||||
|
|
@ -525,15 +527,17 @@ function initiateLiveGridPlayer(monitor,subStreamChannel){
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
//initiate signal check
|
//initiate signal check
|
||||||
var signalCheckInterval = (isNaN(loadedMonitor.details.signal_check) ? 10 : parseFloat(loadedMonitor.details.signal_check)) * 1000 * 60
|
if(streamType !== 'useSubstream'){
|
||||||
if(signalCheckInterval > 0){
|
var signalCheckInterval = (isNaN(loadedMonitor.details.signal_check) ? 10 : parseFloat(loadedMonitor.details.signal_check)) * 1000 * 60
|
||||||
clearInterval(loadedPlayer.signal)
|
if(signalCheckInterval > 0){
|
||||||
loadedPlayer.signal = setInterval(function(){
|
clearInterval(loadedPlayer.signal)
|
||||||
signalCheckLiveStream({
|
loadedPlayer.signal = setInterval(function(){
|
||||||
mid: monitorId,
|
signalCheckLiveStream({
|
||||||
checkSpeed: 1000,
|
mid: monitorId,
|
||||||
})
|
checkSpeed: 1000,
|
||||||
},signalCheckInterval);
|
})
|
||||||
|
},signalCheckInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function revokeVideoPlayerUrl(monitorId){
|
function revokeVideoPlayerUrl(monitorId){
|
||||||
|
|
@ -602,11 +606,7 @@ function closeAllLiveGridPlayers(rememberClose){
|
||||||
$.each(watchedOn,function(n,groupOfMons){
|
$.each(watchedOn,function(n,groupOfMons){
|
||||||
$.each(groupOfMons,function(monitorId,monitor){
|
$.each(groupOfMons,function(monitorId,monitor){
|
||||||
if(monitor === 1){
|
if(monitor === 1){
|
||||||
if(rememberClose){
|
mainSocket.f({f:'monitor',ff:'watch_off',id: monitorId})
|
||||||
mainSocket.f({f:'monitor',ff:'watch_off',id: monitorId})
|
|
||||||
}else{
|
|
||||||
closeLiveGridPlayer(monitorId,true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -817,15 +817,6 @@ $(document).ready(function(e){
|
||||||
.resize(function(){
|
.resize(function(){
|
||||||
resetAllLiveGridDimensionsInMemory()
|
resetAllLiveGridDimensionsInMemory()
|
||||||
})
|
})
|
||||||
.on('click','.toggle-substream',function(){
|
|
||||||
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
|
||||||
var monitor = loadedMonitors[monitorId]
|
|
||||||
if(monitor.subStreamToggleLock)return false;
|
|
||||||
monitor.subStreamToggleLock = true
|
|
||||||
$.get(getApiPrefix(`toggleSubstream`) + '/' + monitor.mid,function(data){
|
|
||||||
monitor.subStreamToggleLock = false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.on('click','.launch-live-grid-monitor',function(){
|
.on('click','.launch-live-grid-monitor',function(){
|
||||||
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
||||||
// if(isMobile){
|
// if(isMobile){
|
||||||
|
|
@ -868,9 +859,11 @@ $(document).ready(function(e){
|
||||||
})
|
})
|
||||||
.on('click','.toggle-live-grid-monitor-logs',function(){
|
.on('click','.toggle-live-grid-monitor-logs',function(){
|
||||||
var monitorItem = $(this).parents('[data-mid]')
|
var monitorItem = $(this).parents('[data-mid]')
|
||||||
|
var monitorId = monitorItem.attr('data-mid')
|
||||||
monitorItem.toggleClass('show_data')
|
monitorItem.toggleClass('show_data')
|
||||||
var dataBlocks = monitorItem.find('.stream-block,.mdl-data_window')
|
var dataBlocks = monitorItem.find('.stream-block,.mdl-data_window')
|
||||||
if(monitorItem.hasClass('show_data')){
|
if(monitorItem.hasClass('show_data')){
|
||||||
|
loadVideoMiniList(monitorId)
|
||||||
dataBlocks.addClass('col-md-6').removeClass('col-md-12')
|
dataBlocks.addClass('col-md-6').removeClass('col-md-12')
|
||||||
}else{
|
}else{
|
||||||
dataBlocks.addClass('col-md-12').removeClass('col-md-6')
|
dataBlocks.addClass('col-md-12').removeClass('col-md-6')
|
||||||
|
|
@ -897,6 +890,10 @@ $(document).ready(function(e){
|
||||||
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
||||||
popOutMonitor(monitorId)
|
popOutMonitor(monitorId)
|
||||||
})
|
})
|
||||||
|
.on('click','.toggle-monitor-substream',function(){
|
||||||
|
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
||||||
|
toggleSubStream(monitorId)
|
||||||
|
})
|
||||||
.on('click','.run-live-grid-monitor-ptz',function(){
|
.on('click','.run-live-grid-monitor-ptz',function(){
|
||||||
var el = $(this)
|
var el = $(this)
|
||||||
var monitorId = el.parents('[data-mid]').attr('data-mid')
|
var monitorId = el.parents('[data-mid]').attr('data-mid')
|
||||||
|
|
@ -925,6 +922,9 @@ $(document).ready(function(e){
|
||||||
ff: 'watch_off',
|
ff: 'watch_off',
|
||||||
id: monitor.mid
|
id: monitor.mid
|
||||||
})
|
})
|
||||||
|
setTimeout(function(){
|
||||||
|
saveLiveGridBlockOpenState(monitorId,$user.ke,0)
|
||||||
|
},1000)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
liveGrid
|
liveGrid
|
||||||
|
|
@ -990,9 +990,17 @@ $(document).ready(function(e){
|
||||||
break;
|
break;
|
||||||
case'monitor_watch_on':
|
case'monitor_watch_on':
|
||||||
var monitorId = d.mid || d.id
|
var monitorId = d.mid || d.id
|
||||||
|
var loadedMonitor = loadedMonitors[monitorId]
|
||||||
var subStreamChannel = d.subStreamChannel
|
var subStreamChannel = d.subStreamChannel
|
||||||
drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel)
|
if(!loadedMonitor.subStreamChannel && loadedMonitor.details.stream_type === 'useSubstream'){
|
||||||
saveLiveGridBlockOpenState(monitorId,$user.ke,1)
|
toggleSubStream(monitorId,function(){
|
||||||
|
drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel)
|
||||||
|
saveLiveGridBlockOpenState(monitorId,$user.ke,1)
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel)
|
||||||
|
saveLiveGridBlockOpenState(monitorId,$user.ke,1)
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case'mode_jpeg_off':
|
case'mode_jpeg_off':
|
||||||
window.jpegModeOn = false
|
window.jpegModeOn = false
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,7 @@ function initiateLivePlayer(monitor){
|
||||||
function createSteamNow(){
|
function createSteamNow(){
|
||||||
clearTimeout(loadedPlayer.m3uCheck)
|
clearTimeout(loadedPlayer.m3uCheck)
|
||||||
var url = getApiPrefix(`hls`) + '/' + monitor.mid + '/s.m3u8'
|
var url = getApiPrefix(`hls`) + '/' + monitor.mid + '/s.m3u8'
|
||||||
$.get(url,function(m3u){
|
$.getJSON(url,function(m3u){
|
||||||
if(m3u == 'File Not Found'){
|
if(m3u == 'File Not Found'){
|
||||||
loadedPlayer.m3uCheck = setTimeout(function(){
|
loadedPlayer.m3uCheck = setTimeout(function(){
|
||||||
createSteamNow()
|
createSteamNow()
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ $(document).ready(function(e){
|
||||||
selectedLogType = selectedLogType === 'all' ? '' : selectedLogType
|
selectedLogType = selectedLogType === 'all' ? '' : selectedLogType
|
||||||
var currentDateRange = dateRangeSelector.data('daterangepicker');
|
var currentDateRange = dateRangeSelector.data('daterangepicker');
|
||||||
var apiEndpoint = getApiPrefix(`logs`) + '/' + selectedLogType + '?start=' + formattedTimeForFilename(currentDateRange.startDate) + '&end=' + formattedTimeForFilename(currentDateRange.endDate)
|
var apiEndpoint = getApiPrefix(`logs`) + '/' + selectedLogType + '?start=' + formattedTimeForFilename(currentDateRange.startDate) + '&end=' + formattedTimeForFilename(currentDateRange.endDate)
|
||||||
$.get(apiEndpoint,function(rows){
|
$.getJSON(apiEndpoint,function(rows){
|
||||||
logViewerDataInMemory = {
|
logViewerDataInMemory = {
|
||||||
startDate: currentDateRange.startDate,
|
startDate: currentDateRange.startDate,
|
||||||
endDate: currentDateRange.endDate,
|
endDate: currentDateRange.endDate,
|
||||||
|
|
|
||||||
|
|
@ -214,7 +214,43 @@ function generateDefaultMonitorSettings(){
|
||||||
"detector_cascades": "",
|
"detector_cascades": "",
|
||||||
"stream_channels": "",
|
"stream_channels": "",
|
||||||
"input_maps": "",
|
"input_maps": "",
|
||||||
"input_map_choices": ""
|
"input_map_choices": "",
|
||||||
|
"substream": {
|
||||||
|
"input": {
|
||||||
|
"type": "h264",
|
||||||
|
"fulladdress": "",
|
||||||
|
"sfps": "",
|
||||||
|
"aduration": "",
|
||||||
|
"probesize": "",
|
||||||
|
"stream_loop": null,
|
||||||
|
"rtsp_transport": "",
|
||||||
|
"accelerator": "0",
|
||||||
|
"hwaccel": null,
|
||||||
|
"hwaccel_vcodec": "",
|
||||||
|
"hwaccel_device": "",
|
||||||
|
"cust_input": ""
|
||||||
|
},
|
||||||
|
"output": {
|
||||||
|
"stream_type": "hls",
|
||||||
|
"rtmp_server_url": "",
|
||||||
|
"rtmp_stream_key": "",
|
||||||
|
"stream_mjpeg_clients": "",
|
||||||
|
"stream_vcodec": "copy",
|
||||||
|
"stream_acodec": "no",
|
||||||
|
"hls_time": "",
|
||||||
|
"hls_list_size": "",
|
||||||
|
"preset_stream": "",
|
||||||
|
"stream_quality": "",
|
||||||
|
"stream_v_br": "",
|
||||||
|
"stream_a_br": "",
|
||||||
|
"stream_fps": "",
|
||||||
|
"stream_scale_x": "640",
|
||||||
|
"stream_scale_y": "480",
|
||||||
|
"stream_rotate": null,
|
||||||
|
"svf": "",
|
||||||
|
"cust_stream": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"shto": "[]",
|
"shto": "[]",
|
||||||
"shfr": "[]"
|
"shfr": "[]"
|
||||||
|
|
@ -372,6 +408,7 @@ window.getMonitorEditFormFields = function(){
|
||||||
monitorConfig.details = safeJsonParse(monitorConfig.details)
|
monitorConfig.details = safeJsonParse(monitorConfig.details)
|
||||||
monitorConfig.details.substream = getSubStreamChannelFields()
|
monitorConfig.details.substream = getSubStreamChannelFields()
|
||||||
monitorConfig.details.groups = getMonitorGroupsSelected()
|
monitorConfig.details.groups = getMonitorGroupsSelected()
|
||||||
|
monitorConfig.details.input_map_choices = monitorSectionInputMapsave()
|
||||||
// TODO : Input Maps and Stream Channels (does old way at the moment)
|
// TODO : Input Maps and Stream Channels (does old way at the moment)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -434,20 +471,24 @@ function drawStreamChannelHtml(options){
|
||||||
monitorStreamChannels.find('.stream-channel').last().find('[channel-detail="stream_vcodec"]').change()
|
monitorStreamChannels.find('.stream-channel').last().find('[channel-detail="stream_vcodec"]').change()
|
||||||
return tempID;
|
return tempID;
|
||||||
}
|
}
|
||||||
|
function replaceMap(string,mapNumber){
|
||||||
|
var newString = string.split(':')
|
||||||
|
newString[0] = `${mapNumber}`
|
||||||
|
return newString.join(':')
|
||||||
|
}
|
||||||
|
function replaceMapInName(string,mapNumber){
|
||||||
|
var newString = string.split('(')
|
||||||
|
newString[1] = replaceMap(newString[1],mapNumber)
|
||||||
|
var lastIndex = newString.length - 1
|
||||||
|
if(!newString[lastIndex].endsWith(')')){
|
||||||
|
newString[lastIndex] = newString + ')'
|
||||||
|
}
|
||||||
|
return newString.join('(')
|
||||||
|
}
|
||||||
function buildMapSelectorOptionsBasedOnAddedMaps(){
|
function buildMapSelectorOptionsBasedOnAddedMaps(){
|
||||||
var baseOptionSet = definitions['Monitor Settings'].blocks.Input.info.find((item) => {return item.name === 'detail=primary_input'}).possible
|
var baseOptionSet = definitions['Monitor Settings'].blocks.Input.info.find((item) => {return item.name === 'detail=primary_input'}).possible
|
||||||
var newOptGroup = [baseOptionSet]
|
var newOptGroup = [baseOptionSet]
|
||||||
var addedInputMaps = monitorEditorWindow.find('.input-map')
|
var addedInputMaps = monitorEditorWindow.find('.input-map')
|
||||||
function replaceMap(string,mapNumber){
|
|
||||||
var newString = string.split(':')
|
|
||||||
newString[0] = `${mapNumber}`
|
|
||||||
return newString.join(':')
|
|
||||||
}
|
|
||||||
function replaceMapInName(string,mapNumber){
|
|
||||||
var newString = string.split('(')
|
|
||||||
newString[1] = replaceMap(newString[1],mapNumber)
|
|
||||||
return newString.join('(')
|
|
||||||
}
|
|
||||||
$.each(addedInputMaps,function(n){
|
$.each(addedInputMaps,function(n){
|
||||||
var mapNumber = n + 1
|
var mapNumber = n + 1
|
||||||
var newOptionSet = []
|
var newOptionSet = []
|
||||||
|
|
@ -464,7 +505,7 @@ function buildMapSelectorOptionsBasedOnAddedMaps(){
|
||||||
function drawInputMapSelectorHtml(options,parent){
|
function drawInputMapSelectorHtml(options,parent){
|
||||||
if(!options.map)options.map = '';
|
if(!options.map)options.map = '';
|
||||||
var availableInputMapSelections = buildMapSelectorOptionsBasedOnAddedMaps()
|
var availableInputMapSelections = buildMapSelectorOptionsBasedOnAddedMaps()
|
||||||
var html = `<div class="form-group map-row d-flex flex-row">
|
var html = `<div class="map-row form-group map-row d-flex flex-row">
|
||||||
<div class="flex-grow-1">
|
<div class="flex-grow-1">
|
||||||
<select class="form-control form-control-sm" map-input="map">`
|
<select class="form-control form-control-sm" map-input="map">`
|
||||||
$.each(availableInputMapSelections,function(n,optgroup){
|
$.each(availableInputMapSelections,function(n,optgroup){
|
||||||
|
|
@ -473,6 +514,7 @@ function drawInputMapSelectorHtml(options,parent){
|
||||||
html += createOptionHtml({
|
html += createOptionHtml({
|
||||||
label: option.name,
|
label: option.name,
|
||||||
value: option.value,
|
value: option.value,
|
||||||
|
selected: option.value === options.map,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
html += `</optgroup>`
|
html += `</optgroup>`
|
||||||
|
|
@ -499,12 +541,7 @@ function importIntoMonitorEditor(options){
|
||||||
//get maps
|
//get maps
|
||||||
monitorSectionInputMaps.empty()
|
monitorSectionInputMaps.empty()
|
||||||
if(monitorDetails.input_maps && monitorDetails.input_maps !== ''){
|
if(monitorDetails.input_maps && monitorDetails.input_maps !== ''){
|
||||||
var input_maps
|
var input_maps = safeJsonParse(monitorDetails.input_maps)
|
||||||
try{
|
|
||||||
input_maps = safeJsonParse(monitorDetails.input_maps)
|
|
||||||
}catch(er){
|
|
||||||
input_maps = monitorDetails.input_maps;
|
|
||||||
}
|
|
||||||
if(input_maps.length > 0){
|
if(input_maps.length > 0){
|
||||||
showInputMappingFields()
|
showInputMappingFields()
|
||||||
$.each(input_maps,function(n,v){
|
$.each(input_maps,function(n,v){
|
||||||
|
|
@ -737,7 +774,7 @@ var mapPlacementInit = function(){
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
var monitorSectionInputMapsave = function(){
|
var monitorSectionInputMapsave = function(){
|
||||||
var mapContainers = $('[input-mapping]');
|
var mapContainers = monitorEditorWindow.find('[input-mapping]');
|
||||||
var stringForSave = {}
|
var stringForSave = {}
|
||||||
mapContainers.each(function(q,t){
|
mapContainers.each(function(q,t){
|
||||||
var mapRowElement = $(t).find('.map-row');
|
var mapRowElement = $(t).find('.map-row');
|
||||||
|
|
@ -751,7 +788,7 @@ var monitorSectionInputMapsave = function(){
|
||||||
});
|
});
|
||||||
stringForSave[$(t).attr('input-mapping')] = mapRow;
|
stringForSave[$(t).attr('input-mapping')] = mapRow;
|
||||||
});
|
});
|
||||||
monitorEditorWindow.find('[detail="input_map_choices"]').val(JSON.stringify(stringForSave)).change();
|
return stringForSave
|
||||||
}
|
}
|
||||||
monitorSectionInputMaps.on('click','.delete',function(){
|
monitorSectionInputMaps.on('click','.delete',function(){
|
||||||
$(this).parents('.input-map').remove()
|
$(this).parents('.input-map').remove()
|
||||||
|
|
@ -779,14 +816,9 @@ monitorEditorWindow.on('change','[map-detail]',function(){
|
||||||
})
|
})
|
||||||
monitorEditorWindow.on('click','[input-mapping] .add_map_row',function(){
|
monitorEditorWindow.on('click','[input-mapping] .add_map_row',function(){
|
||||||
drawInputMapSelectorHtml({},$(this).parents('[input-mapping]').find('.choices'))
|
drawInputMapSelectorHtml({},$(this).parents('[input-mapping]').find('.choices'))
|
||||||
monitorSectionInputMapsave()
|
|
||||||
})
|
})
|
||||||
monitorEditorWindow.on('click','[input-mapping] .delete_map_row',function(){
|
monitorEditorWindow.on('click','[input-mapping] .delete_map_row',function(){
|
||||||
$(this).parents('.map-row').remove()
|
$(this).parents('.map-row').remove()
|
||||||
monitorSectionInputMapsave()
|
|
||||||
})
|
|
||||||
monitorEditorWindow.on('change','[map-input]',function(){
|
|
||||||
monitorSectionInputMapsave()
|
|
||||||
})
|
})
|
||||||
//////////////////
|
//////////////////
|
||||||
//Stream Channels
|
//Stream Channels
|
||||||
|
|
@ -808,7 +840,6 @@ var channelPlacementInit = function(){
|
||||||
_this.attr('stream-channel',n)
|
_this.attr('stream-channel',n)
|
||||||
_this.find('.place').text(n)
|
_this.find('.place').text(n)
|
||||||
_this.find('[input-mapping]').attr('input-mapping','stream_channel-'+n)
|
_this.find('[input-mapping]').attr('input-mapping','stream_channel-'+n)
|
||||||
monitorSectionInputMapsave()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
var getSubStreamChannelFields = function(){
|
var getSubStreamChannelFields = function(){
|
||||||
|
|
@ -1064,7 +1095,7 @@ editorForm.find('[name="type"]').change(function(e){
|
||||||
}
|
}
|
||||||
// presets
|
// presets
|
||||||
var loadPresets = function(callback){
|
var loadPresets = function(callback){
|
||||||
$.get(getApiPrefix() + '/monitorStates/' + $user.ke,function(d){
|
$.getJSON(getApiPrefix() + '/monitorStates/' + $user.ke,function(d){
|
||||||
var presets = d.presets
|
var presets = d.presets
|
||||||
loadedPresets = {}
|
loadedPresets = {}
|
||||||
$.each(presets,function(n,preset){
|
$.each(presets,function(n,preset){
|
||||||
|
|
@ -1461,5 +1492,17 @@ editorForm.find('[name="type"]').change(function(e){
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
function checkToOpenSideMenu(){
|
||||||
|
if(isSideBarMenuCollapsed()){
|
||||||
|
sideMenuCollapsePoint.collapse('show')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addOnTabOpen('monitorSettings', checkToOpenSideMenu)
|
||||||
|
addOnTabReopen('monitorSettings', checkToOpenSideMenu)
|
||||||
|
addOnTabAway('monitorSettings', function(){
|
||||||
|
if(isSideBarMenuCollapsed()){
|
||||||
|
sideMenuCollapsePoint.collapse('hide')
|
||||||
|
}
|
||||||
|
})
|
||||||
window.generateDefaultMonitorSettings = generateDefaultMonitorSettings
|
window.generateDefaultMonitorSettings = generateDefaultMonitorSettings
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ $(document).ready(function(){
|
||||||
var monitorStatesMonitors = $('#monitorStatesMonitors')
|
var monitorStatesMonitors = $('#monitorStatesMonitors')
|
||||||
var theForm = monitorStatesPage.find('form')
|
var theForm = monitorStatesPage.find('form')
|
||||||
function loadMonitorStatesPresets(callback){
|
function loadMonitorStatesPresets(callback){
|
||||||
$.get(getApiPrefix(`monitorStates`),function(d){
|
$.getJSON(getApiPrefix(`monitorStates`),function(d){
|
||||||
var html = ''
|
var html = ''
|
||||||
$.each(d.presets,function(n,v){
|
$.each(d.presets,function(n,v){
|
||||||
monitorStatesLoaded[v.name] = v
|
monitorStatesLoaded[v.name] = v
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ $(document).ready(function(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function drawMonitorsListApiKeyList(){
|
function drawMonitorsListApiKeyList(){
|
||||||
$.get(getApiPrefix(`api`) + '/list',function(d){
|
$.getJSON(getApiPrefix(`api`) + '/list',function(d){
|
||||||
var html = ''
|
var html = ''
|
||||||
$.each(d.keys || [],function(n,key){
|
$.each(d.keys || [],function(n,key){
|
||||||
console.log(key)
|
console.log(key)
|
||||||
|
|
@ -169,7 +169,7 @@ $(document).ready(function(){
|
||||||
var el = thisEl.parents('[data-mid]')
|
var el = thisEl.parents('[data-mid]')
|
||||||
var monitorId = el.attr('data-mid')
|
var monitorId = el.attr('data-mid')
|
||||||
var mode = thisEl.attr('set-mode')
|
var mode = thisEl.attr('set-mode')
|
||||||
$.get(`${getApiPrefix('monitor')}/${monitorId}/${mode}`,function(data){
|
$.getJSON(`${getApiPrefix('monitor')}/${monitorId}/${mode}`,function(data){
|
||||||
console.log(data)
|
console.log(data)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ function getVideoSnapshot(videoElement,cb){
|
||||||
function runPtzCommand(monitorId,switchChosen){
|
function runPtzCommand(monitorId,switchChosen){
|
||||||
switch(switchChosen){
|
switch(switchChosen){
|
||||||
case'setHome':
|
case'setHome':
|
||||||
$.get(getApiPrefix(`control`) + '/' + monitorId + '/setHome',function(data){
|
$.getJSON(getApiPrefix(`control`) + '/' + monitorId + '/setHome',function(data){
|
||||||
console.log(data)
|
console.log(data)
|
||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
|
|
@ -170,7 +170,26 @@ function runTestDetectionTrigger(monitorId,callback){
|
||||||
if(callback)callback()
|
if(callback)callback()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
function toggleSubStream(monitorId,callback){
|
||||||
|
var monitor = loadedMonitors[monitorId]
|
||||||
|
var substreamConfig = monitor.details.substream
|
||||||
|
var isSubStreamConfigured = !!substreamConfig.output;
|
||||||
|
if(!isSubStreamConfigured){
|
||||||
|
new PNotify({
|
||||||
|
type: 'warning',
|
||||||
|
title: lang['Invalid Settings'],
|
||||||
|
text: lang.SubstreamNotConfigured,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(monitor.subStreamToggleLock)return false;
|
||||||
|
monitor.subStreamToggleLock = true
|
||||||
|
$.getJSON(getApiPrefix() + '/toggleSubstream/'+$user.ke+'/'+monitorId,function(d){
|
||||||
|
monitor.subStreamToggleLock = false
|
||||||
|
debugLog(d)
|
||||||
|
if(callback)callback()
|
||||||
|
})
|
||||||
|
}
|
||||||
function playAudioAlert(){
|
function playAudioAlert(){
|
||||||
var fileName = $user.details.audio_alert
|
var fileName = $user.details.audio_alert
|
||||||
if(fileName && window.soundAlarmed !== true){
|
if(fileName && window.soundAlarmed !== true){
|
||||||
|
|
@ -195,7 +214,7 @@ function playAudioAlert(){
|
||||||
|
|
||||||
function buildStreamUrl(monitorId){
|
function buildStreamUrl(monitorId){
|
||||||
var monitor = loadedMonitors[monitorId]
|
var monitor = loadedMonitors[monitorId]
|
||||||
var streamURL
|
var streamURL = ''
|
||||||
var streamType = safeJsonParse(monitor.details).stream_type
|
var streamType = safeJsonParse(monitor.details).stream_type
|
||||||
switch(streamType){
|
switch(streamType){
|
||||||
case'jpeg':
|
case'jpeg':
|
||||||
|
|
@ -216,6 +235,9 @@ function buildStreamUrl(monitorId){
|
||||||
case'b64':
|
case'b64':
|
||||||
streamURL = 'Websocket'
|
streamURL = 'Websocket'
|
||||||
break;
|
break;
|
||||||
|
case'useSubstream':
|
||||||
|
streamURL = lang['Use Substream']
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if(!streamURL){
|
if(!streamURL){
|
||||||
$.each(onBuildStreamUrlExtensions,function(n,extender){
|
$.each(onBuildStreamUrlExtensions,function(n,extender){
|
||||||
|
|
@ -285,7 +307,7 @@ function importM3u8Playlist(textData){
|
||||||
})
|
})
|
||||||
$.each(parsedList,function(name,url){
|
$.each(parsedList,function(name,url){
|
||||||
var link = getUrlPieces(url)
|
var link = getUrlPieces(url)
|
||||||
var newMon = $.aM.generateDefaultMonitorSettings()
|
var newMon = generateDefaultMonitorSettings()
|
||||||
newMon.details = JSON.parse(newMon.details)
|
newMon.details = JSON.parse(newMon.details)
|
||||||
newMon.mid = 'HLS' + name.toLowerCase()
|
newMon.mid = 'HLS' + name.toLowerCase()
|
||||||
newMon.name = name
|
newMon.name = name
|
||||||
|
|
@ -388,7 +410,7 @@ function deleteMonitors(monitorsSelected,afterDelete){
|
||||||
class:'btn-danger',
|
class:'btn-danger',
|
||||||
callback:function(){
|
callback:function(){
|
||||||
$.each(monitorsSelected,function(n,monitor){
|
$.each(monitorsSelected,function(n,monitor){
|
||||||
$.get(`${getApiPrefix(`configureMonitor`)}/${monitor.mid}/delete`,function(data){
|
$.getJSON(`${getApiPrefix(`configureMonitor`)}/${monitor.mid}/delete`,function(data){
|
||||||
console.log(data)
|
console.log(data)
|
||||||
if(monitorsSelected.length === n + 1){
|
if(monitorsSelected.length === n + 1){
|
||||||
//last
|
//last
|
||||||
|
|
@ -403,7 +425,7 @@ function deleteMonitors(monitorsSelected,afterDelete){
|
||||||
class:'btn-danger',
|
class:'btn-danger',
|
||||||
callback:function(){
|
callback:function(){
|
||||||
$.each(monitorsSelected,function(n,monitor){
|
$.each(monitorsSelected,function(n,monitor){
|
||||||
$.get(`${getApiPrefix(`configureMonitor`)}/${monitor.mid}/delete?deleteFiles=true`,function(data){
|
$.getJSON(`${getApiPrefix(`configureMonitor`)}/${monitor.mid}/delete?deleteFiles=true`,function(data){
|
||||||
console.log(data)
|
console.log(data)
|
||||||
if(monitorsSelected.length === n + 1){
|
if(monitorsSelected.length === n + 1){
|
||||||
//last
|
//last
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,310 @@
|
||||||
|
$(document).ready(function(){
|
||||||
|
var selectedMonitorId
|
||||||
|
var loadedVideoEncoders = {}
|
||||||
|
var blockWindow = $('#tab-onvifDeviceManager')
|
||||||
|
var blockWindowInfo = $('#onvifDeviceManagerInfo')
|
||||||
|
var blockForm = blockWindow.find('form')
|
||||||
|
var monitorsList = blockWindow.find('.monitors_list')
|
||||||
|
var dateRangePicker = blockForm.find('[name="utcDateTime"]')
|
||||||
|
var convertFormFieldNameToObjectKeys = function(formFields){
|
||||||
|
var newObject = {}
|
||||||
|
$.each(formFields,function(key,value){
|
||||||
|
var keyPieces = key.split(':')
|
||||||
|
var parent = null
|
||||||
|
$.each(keyPieces,function(n,piece){
|
||||||
|
if(!parent){
|
||||||
|
parent = newObject
|
||||||
|
}
|
||||||
|
if(keyPieces[n + 1]){
|
||||||
|
if(!parent[piece])parent[piece] = {}
|
||||||
|
parent = parent[piece]
|
||||||
|
}else{
|
||||||
|
parent[piece] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return newObject
|
||||||
|
}
|
||||||
|
var converObjectKeysToFormFieldName = (object,parentKey) => {
|
||||||
|
parentKey = parentKey ? parentKey : ''
|
||||||
|
var theList = {}
|
||||||
|
Object.keys(object).forEach((key) => {
|
||||||
|
var value = object[key]
|
||||||
|
var newKey = parentKey ? parentKey + ':' + key : key
|
||||||
|
if(typeof value === 'string'){
|
||||||
|
theList[newKey] = value
|
||||||
|
}else if(value instanceof Object || value instanceof Array){
|
||||||
|
theList = Object.assign(theList,converObjectKeysToFormFieldName(value,newKey))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return theList
|
||||||
|
}
|
||||||
|
var setIntegerGuider = function(fieldName,theRange){
|
||||||
|
blockForm.find(`[${fieldName}]`)
|
||||||
|
.attr('min',theRange.Min)
|
||||||
|
.attr('max',theRange.Max)
|
||||||
|
.attr('placeholder',`Mininum : ${theRange.Min}, Maximum: ${theRange.Max}`);
|
||||||
|
}
|
||||||
|
var setGuidersInFormFields = function(onvifData){
|
||||||
|
if(onvifData.videoEncoderOptions){
|
||||||
|
var encoderOptions = onvifData.videoEncoderOptions
|
||||||
|
//Encoding
|
||||||
|
var hasH264 = !!encoderOptions.H264;
|
||||||
|
var hasH265 = !!encoderOptions.H265;
|
||||||
|
// var hasJPEG = !!encoderOptions.JPEG;
|
||||||
|
var availableEncoders = []
|
||||||
|
if(hasH264)availableEncoders.push('H264')
|
||||||
|
if(hasH265)availableEncoders.push('H265')
|
||||||
|
// if(hasJPEG)availableEncoders.push('JPEG')
|
||||||
|
var html = ``
|
||||||
|
$.each(availableEncoders,function(n,encoder){
|
||||||
|
html += `<option value="${encoder}">${encoder}</option>`
|
||||||
|
})
|
||||||
|
blockForm.find('[name="Encoding"]').html(html)
|
||||||
|
//Resolutions
|
||||||
|
console.log(encoderOptions)
|
||||||
|
if(encoderOptions.H264){
|
||||||
|
var html = ``
|
||||||
|
$.each(encoderOptions.H264.ResolutionsAvailable,function(n,resolution){
|
||||||
|
html += `<option value="${resolution.Width}x${resolution.Height}">${resolution.Width}x${resolution.Height}</option>`
|
||||||
|
})
|
||||||
|
blockForm.find('[detail="Resolution"]').html(html)
|
||||||
|
//Profiles Supported
|
||||||
|
var html = ``
|
||||||
|
var profilesSupported = encoderOptions.H264.H264ProfilesSupported
|
||||||
|
profilesSupported = typeof profilesSupported === 'string' ? [profilesSupported] : profilesSupported
|
||||||
|
$.each(profilesSupported,function(n,profile){
|
||||||
|
html += `<option value="${profile}">${profile}</option>`
|
||||||
|
})
|
||||||
|
blockForm.find('[name="H264:H264Profile"]').html(html)
|
||||||
|
//GOV Length, Frame Rate, Encoding Interval Range
|
||||||
|
setIntegerGuider('name="H264:GovLength"',encoderOptions.H264.GovLengthRange)
|
||||||
|
setIntegerGuider('name="RateControl:FrameRateLimit"',encoderOptions.H264.FrameRateRange)
|
||||||
|
setIntegerGuider('name="RateControl:EncodingInterval"',encoderOptions.H264.EncodingIntervalRange)
|
||||||
|
}
|
||||||
|
setIntegerGuider('name="Quality"',encoderOptions.QualityRange)
|
||||||
|
}
|
||||||
|
if(onvifData.videoEncoders){
|
||||||
|
loadedVideoEncoders = {}
|
||||||
|
var html = ``
|
||||||
|
onvifData.videoEncoders.forEach((encoder) => {
|
||||||
|
html += `<option value="${encoder.$.token}">${encoder.Name}</option>`
|
||||||
|
loadedVideoEncoders[encoder.$.token] = encoder
|
||||||
|
})
|
||||||
|
blockForm.find('[name=videoToken]').html(html)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var writeOnvifDataToFormFields = function(onvifData){
|
||||||
|
var formFields = {}
|
||||||
|
console.log('write fields ',onvifData)
|
||||||
|
if(onvifData.date){
|
||||||
|
var utcDatePieces = onvifData.date.UTCDateTime
|
||||||
|
var dateString = `${utcDatePieces.Date.Year}-${utcDatePieces.Date.Month}-${utcDatePieces.Date.Day} ${utcDatePieces.Time.Hour}:${utcDatePieces.Time.Minute}:${utcDatePieces.Time.Second} UTC`
|
||||||
|
var parsedDate = new Date(dateString);
|
||||||
|
console.log(dateString,parsedDate)
|
||||||
|
formFields["utcDateTime"] = parsedDate
|
||||||
|
formFields["dateTimeType"] = onvifData.date.DateTimeType
|
||||||
|
formFields["daylightSavings"] = onvifData.date.DaylightSavings
|
||||||
|
formFields["timezone"] = onvifData.date.TimeZone.TZ
|
||||||
|
|
||||||
|
}
|
||||||
|
if(onvifData.networkInterface){
|
||||||
|
var eth0 = onvifData.networkInterface[0] || onvifData.networkInterface
|
||||||
|
var ipConfig = eth0.IPv4.Config
|
||||||
|
var linkLocal = ipConfig.LinkLocal || ipConfig.FromDHCP
|
||||||
|
var ipv4 = ipConfig.DHCP === 'true' ? linkLocal.Address : ipConfig.Manual.Address || linkLocal.Address
|
||||||
|
formFields["setNetworkInterface:ipv4"] = ipv4
|
||||||
|
}
|
||||||
|
if(onvifData.gateway){
|
||||||
|
formFields["setGateway:ipv4"] = onvifData.gateway
|
||||||
|
}
|
||||||
|
if(onvifData.hostname){
|
||||||
|
formFields["setHostname:name"] = onvifData.hostname
|
||||||
|
}
|
||||||
|
if(onvifData.dns && onvifData.dns.DNSManual){
|
||||||
|
var dnsList = onvifData.dns.DNSManual
|
||||||
|
if(dnsList.IPv4Address){
|
||||||
|
dnsList = dnsList.IPv4Address
|
||||||
|
}else if(dnsList[0]){
|
||||||
|
dnsList = dnsList.map((item) => {
|
||||||
|
return item.IPv4Address
|
||||||
|
}).join(',')
|
||||||
|
}else{
|
||||||
|
dnsList = ''
|
||||||
|
}
|
||||||
|
formFields["setDNS:dns"] = dnsList
|
||||||
|
}
|
||||||
|
if(onvifData.ntp && onvifData.ntp.NTPManual){
|
||||||
|
var ntpIp = onvifData.ntp.NTPManual.IPv4Address
|
||||||
|
formFields["setNTP:ipv4"] = ntpIp
|
||||||
|
}
|
||||||
|
if(onvifData.protocols){
|
||||||
|
onvifData.protocols.forEach((protocol) => {
|
||||||
|
//RTSP, HTTP
|
||||||
|
formFields[`setProtocols:${protocol.Name}`] = protocol.Port
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(onvifData.videoEncoders){
|
||||||
|
setFieldsFromOnvifKeys(onvifData.videoEncoders[0])
|
||||||
|
}
|
||||||
|
if(onvifData.imagingSettings && onvifData.imagingSettings.ok !== false){
|
||||||
|
$('#Imaging').find('.form-group').hide()
|
||||||
|
setFieldsFromOnvifKeys(onvifData.imagingSettings)
|
||||||
|
$.each(onvifData.imagingSettings,function(key){
|
||||||
|
$('#Imaging').find(`[name="${key}"]`).parents('.form-group').show()
|
||||||
|
})
|
||||||
|
$('#Imaging').show()
|
||||||
|
}else{
|
||||||
|
$('#Imaging').hide()
|
||||||
|
}
|
||||||
|
Object.keys(formFields).forEach((key) => {
|
||||||
|
var value = formFields[key]
|
||||||
|
blockForm.find(`[name="${key}"]`).val(value)
|
||||||
|
})
|
||||||
|
if(onvifData.date)dateRangePicker.data('daterangepicker').setStartDate(dateString)
|
||||||
|
}
|
||||||
|
var setFieldsFromOnvifKeys = function(encoder){
|
||||||
|
var formFields = converObjectKeysToFormFieldName(encoder)
|
||||||
|
Object.keys(formFields).forEach((key) => {
|
||||||
|
var value = formFields[key]
|
||||||
|
blockForm.find(`[name="${key}"]`).val(value).parents('.form-group')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var rebootCamera = function(monitorId){
|
||||||
|
$.confirm.create({
|
||||||
|
title: lang['Reboot Camera'],
|
||||||
|
body: lang.noUndoForAction,
|
||||||
|
clickOptions: {
|
||||||
|
title: lang['Reboot'],
|
||||||
|
class: 'btn-warning'
|
||||||
|
},
|
||||||
|
clickCallback: function(){
|
||||||
|
$.get(getApiPrefix('onvifDeviceManager') + '/' + monitorId + '/reboot',function(response){
|
||||||
|
new PNotify({
|
||||||
|
title: lang.rebootingCamera,
|
||||||
|
text: lang['Please Wait...'],
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var getUIFieldValuesFromCamera = function(monitorId){
|
||||||
|
$.get(getApiPrefix('onvifDeviceManager') + '/' + monitorId,function(response){
|
||||||
|
var onvifData = response.onvifData
|
||||||
|
if(onvifData && onvifData.ok === true){
|
||||||
|
blockWindowInfo.html(JSON.stringify(onvifData,null,3))
|
||||||
|
setGuidersInFormFields(onvifData)
|
||||||
|
writeOnvifDataToFormFields(onvifData)
|
||||||
|
}else{
|
||||||
|
new PNotify({
|
||||||
|
title: lang.ONVIFEventsNotAvailable,
|
||||||
|
text: lang.ONVIFEventsNotAvailableText1,
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var getUIFieldValuesFromForm = function(){
|
||||||
|
var newObject = {}
|
||||||
|
var formOptions = blockForm.serializeObject()
|
||||||
|
$.each(formOptions,function(key,value){
|
||||||
|
var enclosingObject = blockForm.find(`[name="${key}"]`).parents('.form-group-group').attr("id")
|
||||||
|
if(key === 'utcDateTime'){
|
||||||
|
//dateRangePicker
|
||||||
|
newObject[enclosingObject + ':' + key] = dateRangePicker.data('daterangepicker').startDate._d
|
||||||
|
console.log(`dateRangePicker.data('daterangepicker').startDate._d`,dateRangePicker.data('daterangepicker').startDate._d)
|
||||||
|
}else{
|
||||||
|
newObject[enclosingObject + ':' + key] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return newObject
|
||||||
|
}
|
||||||
|
function openOnvifDeviceManager(monitorId){
|
||||||
|
selectedMonitorId = `${monitorId}`
|
||||||
|
blockForm.find('input').val('')
|
||||||
|
getUIFieldValuesFromCamera(monitorId)
|
||||||
|
}
|
||||||
|
function submitTheForm(){
|
||||||
|
$.confirm.create({
|
||||||
|
title: lang.updateCamerasInternalSettings,
|
||||||
|
body: lang.noUndoForAction,
|
||||||
|
clickOptions: {
|
||||||
|
title: lang['Save'],
|
||||||
|
class:'btn-success'
|
||||||
|
},
|
||||||
|
clickCallback: function(){
|
||||||
|
var postData = convertFormFieldNameToObjectKeys(getUIFieldValuesFromForm())
|
||||||
|
console.log('postData',postData)
|
||||||
|
$.post(getApiPrefix('onvifDeviceManager') + '/' + selectedMonitorId + '/save',{
|
||||||
|
data: JSON.stringify(postData)
|
||||||
|
},function(response){
|
||||||
|
var notifyTitle = lang['Settings Changed']
|
||||||
|
var notifyText = lang.onvifdeviceSavedText
|
||||||
|
var notifyTextError = ''
|
||||||
|
var notifyType = 'success'
|
||||||
|
$.each(response.responses,function(key,response){
|
||||||
|
if(!response.ok){
|
||||||
|
notifyTextError = lang.onvifdeviceSavedFoundErrorText
|
||||||
|
notifyType = 'warning'
|
||||||
|
console.log(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
notifyText = notifyTextError ? notifyText + ' ' + notifyTextError : notifyText;
|
||||||
|
new PNotify({
|
||||||
|
title: notifyTitle,
|
||||||
|
text: notifyText,
|
||||||
|
type: notifyType,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
dateRangePicker.daterangepicker({
|
||||||
|
singleDatePicker: true,
|
||||||
|
timePicker: true,
|
||||||
|
timePicker24Hour: true,
|
||||||
|
timePickerSeconds: true,
|
||||||
|
// timePickerIncrement: 30,
|
||||||
|
locale: {
|
||||||
|
format: 'YYYY-MM-DD HH:mm:ss'
|
||||||
|
}
|
||||||
|
},function(start, end, label){
|
||||||
|
console.log(start)
|
||||||
|
});
|
||||||
|
$('body').on('click','.open-onvif-device-manager',function(){
|
||||||
|
var monitorId = $(this).attr('data-mid') || $(this).parents('[data-mid]').attr('data-mid');
|
||||||
|
openOnvifDeviceManager(monitorId)
|
||||||
|
})
|
||||||
|
monitorsList.change(function(){
|
||||||
|
var monitorId = $(this).val()
|
||||||
|
openOnvifDeviceManager(monitorId)
|
||||||
|
})
|
||||||
|
blockWindow.on('click','.onvif-device-reboot',function(){
|
||||||
|
rebootCamera(selectedMonitorId)
|
||||||
|
})
|
||||||
|
blockForm.on('change','[name="videoToken"]',function(){
|
||||||
|
var selectedEncoder = loadedVideoEncoders[$(this).val()]
|
||||||
|
setFieldsFromOnvifKeys(selectedEncoder)
|
||||||
|
blockForm.find('[detail="Resolution"]').val(`${selectedEncoder['Resolution:Width']}x${selectedEncoder['Resolution:Height']}`)
|
||||||
|
})
|
||||||
|
blockForm.on('change','[detail="Resolution"]',function(){
|
||||||
|
var resolution = $(this).val().split('x')
|
||||||
|
var width = resolution[0]
|
||||||
|
var height = resolution[1]
|
||||||
|
blockForm.find('[name="Resolution:Width"]').val(width)
|
||||||
|
blockForm.find('[name="Resolution:Height"]').val(height)
|
||||||
|
})
|
||||||
|
blockForm.submit(function(e){
|
||||||
|
e.preventDefault()
|
||||||
|
submitTheForm()
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
onWebSocketEvent(function(d){
|
||||||
|
switch(d.f){
|
||||||
|
case'init_success':
|
||||||
|
drawMonitorListToSelector(monitorsList)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -119,7 +119,7 @@ $(document).ready(function(e){
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
var filterOutMonitorsThatAreAlreadyAdded = function(listOfCameras,callback){
|
var filterOutMonitorsThatAreAlreadyAdded = function(listOfCameras,callback){
|
||||||
$.get(getApiPrefix(`monitor`),function(monitors){
|
$.getJSON(getApiPrefix(`monitor`),function(monitors){
|
||||||
var monitorsNotExisting = []
|
var monitorsNotExisting = []
|
||||||
$.each(listOfCameras,function(n,camera){
|
$.each(listOfCameras,function(n,camera){
|
||||||
var matches = false
|
var matches = false
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,56 @@ $(document).ready(function(){
|
||||||
var theBlock = $('#recentVideos')
|
var theBlock = $('#recentVideos')
|
||||||
var theList = $('#recentVideosList')
|
var theList = $('#recentVideosList')
|
||||||
var monitorList = theBlock.find('.monitors_list')
|
var monitorList = theBlock.find('.monitors_list')
|
||||||
function drawRowToList(row,toBegin){
|
function drawRowToList(row,toBegin,returnLastChild){
|
||||||
theList[toBegin ? 'prepend' : 'append'](createVideoRow(row))
|
theList[toBegin ? 'prepend' : 'append'](createVideoRow(row))
|
||||||
|
if(returnLastChild){
|
||||||
|
var theChildren = theList.children()
|
||||||
|
return toBegin ? theChildren.first() : theChildren.last()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function drawDaysToList(videos,toBegin){
|
||||||
|
var videosSortedByDays = sortVideosByDays(videos)
|
||||||
|
$.each(videosSortedByDays,function(monitorId,days){
|
||||||
|
$.each(days,function(dayKey,videos){
|
||||||
|
var copyOfVideos = ([]).concat(videos).reverse()
|
||||||
|
theList.append(createDayCard(copyOfVideos,dayKey,monitorId))
|
||||||
|
var theChildren = theList.children()
|
||||||
|
var createdCardCarrier = toBegin ? theChildren.first() : theChildren.last()
|
||||||
|
bindFrameFindingByMouseMoveForDay(createdCardCarrier,dayKey,copyOfVideos)
|
||||||
|
preloadAllTimelapseFramesToMemoryFromVideoList(copyOfVideos)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
function loadVideos(options,callback){
|
function loadVideos(options,callback){
|
||||||
theList.empty();
|
theList.empty();
|
||||||
getVideos(options,function(data){
|
var currentDate = new Date()
|
||||||
|
options.startDate = moment(currentDate).subtract(72, 'hours')._d;
|
||||||
|
options.endDate = moment(currentDate)._d;
|
||||||
|
console.log(options)
|
||||||
|
function drawVideoData(data){
|
||||||
var html = ``
|
var html = ``
|
||||||
var videos = data.videos || {}
|
var videos = data.videos || []
|
||||||
$.each(videos,function(n,row){
|
// $.each(videos,function(n,row){
|
||||||
drawRowToList(row)
|
// var createdCardCarrier = drawRowToList(row,false,true)
|
||||||
})
|
// bindFrameFindingByMouseMove(createdCardCarrier,row)
|
||||||
|
// })
|
||||||
|
drawDaysToList(videos,false)
|
||||||
getCountOfEvents({
|
getCountOfEvents({
|
||||||
|
startDate: options.startDate,
|
||||||
|
endDate: options.endDate,
|
||||||
monitorId: options.monitorId,
|
monitorId: options.monitorId,
|
||||||
})
|
})
|
||||||
callback(data)
|
callback(data)
|
||||||
|
}
|
||||||
|
getVideos(options,function(data){
|
||||||
|
if(data.videos.length === 0){
|
||||||
|
options.limit = 20
|
||||||
|
delete(options.startDate)
|
||||||
|
delete(options.endDate)
|
||||||
|
getVideos(options,drawVideoData)
|
||||||
|
}else{
|
||||||
|
drawVideoData(data)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function getCountOfEvents(options){
|
function getCountOfEvents(options){
|
||||||
|
|
@ -43,12 +78,12 @@ $(document).ready(function(){
|
||||||
liveStamp()
|
liveStamp()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
addOnTabReopen('initial', function () {
|
theBlock.find('.recent-videos-refresh').click(function(){
|
||||||
var theSelected = `${monitorList.val()}`
|
var theSelected = `${monitorList.val()}`
|
||||||
drawMonitorListToSelector(monitorList.find('optgroup'))
|
drawMonitorListToSelector(monitorList.find('optgroup'))
|
||||||
monitorList.val(theSelected)
|
monitorList.val(theSelected)
|
||||||
loadVideos({
|
loadVideos({
|
||||||
limit: 10,
|
limit: 20,
|
||||||
monitorId: theSelected || undefined,
|
monitorId: theSelected || undefined,
|
||||||
},function(){
|
},function(){
|
||||||
liveStamp()
|
liveStamp()
|
||||||
|
|
@ -59,15 +94,16 @@ $(document).ready(function(){
|
||||||
case'init_success':
|
case'init_success':
|
||||||
drawMonitorListToSelector(monitorList.find('optgroup'))
|
drawMonitorListToSelector(monitorList.find('optgroup'))
|
||||||
loadVideos({
|
loadVideos({
|
||||||
limit: 10,
|
limit: 20,
|
||||||
},function(){
|
},function(){
|
||||||
liveStamp()
|
liveStamp()
|
||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
case'video_build_success':
|
// case'video_build_success':
|
||||||
loadVideoData(d)
|
// loadVideoData(d)
|
||||||
drawRowToList(createVideoLinks(d),true)
|
// var createdCardCarrier = drawRowToList(createVideoLinks(d),true)
|
||||||
break;
|
// bindFrameFindingByMouseMove(createdCardCarrier,row)
|
||||||
|
// break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ $(document).ready(function(){
|
||||||
var selectedStates = schedulerWindow.find('[name="monitorStates"]')
|
var selectedStates = schedulerWindow.find('[name="monitorStates"]')
|
||||||
var selectedDays = schedulerWindow.find('[name="days"]')
|
var selectedDays = schedulerWindow.find('[name="days"]')
|
||||||
var loadSchedules = function(callback){
|
var loadSchedules = function(callback){
|
||||||
$.get(getApiPrefix() + '/schedule/' + $user.ke,function(d){
|
$.getJSON(getApiPrefix() + '/schedule/' + $user.ke,function(d){
|
||||||
var html = ''
|
var html = ''
|
||||||
$.each(d.schedules,function(n,v){
|
$.each(d.schedules,function(n,v){
|
||||||
loadedSchedules[v.name] = v
|
loadedSchedules[v.name] = v
|
||||||
|
|
@ -21,7 +21,7 @@ $(document).ready(function(){
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
var loadMonitorStates = function(){
|
var loadMonitorStates = function(){
|
||||||
$.get(getApiPrefix() + '/monitorStates/' + $user.ke,function(d){
|
$.getJSON(getApiPrefix() + '/monitorStates/' + $user.ke,function(d){
|
||||||
var html = ''
|
var html = ''
|
||||||
$.each(d.presets,function(n,v){
|
$.each(d.presets,function(n,v){
|
||||||
loadedMonitorStates[v.name] = v
|
loadedMonitorStates[v.name] = v
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ var sidebarMenuInner = $('#menu-side')
|
||||||
var pageTabContainer = $('#pageTabContainer')
|
var pageTabContainer = $('#pageTabContainer')
|
||||||
var topMenu = $('#topMenu')
|
var topMenu = $('#topMenu')
|
||||||
var monitorSideList = $('#monitorSideList')
|
var monitorSideList = $('#monitorSideList')
|
||||||
var toggleSideBarMenu = $('#toggleSideBarMenu')
|
var sideMenuCollapsePoint = $('#side-menu-collapse-point')
|
||||||
function buildTabHtml(tabName,tabLabel,tabIcon){
|
function buildTabHtml(tabName,tabLabel,tabIcon){
|
||||||
return `<li class="nav-item">
|
return `<li class="nav-item">
|
||||||
<a class="nav-link side-menu-link" page-open="${tabName}">
|
<a class="nav-link side-menu-link" page-open="${tabName}">
|
||||||
|
|
@ -97,6 +97,27 @@ function toggleSideMenuVisibility(){
|
||||||
pageTabContainer.addClass('col-md-9 col-lg-10')
|
pageTabContainer.addClass('col-md-9 col-lg-10')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function toggleSideMenuCollapse(dontSaveChange){
|
||||||
|
var isVisible = sideMenuCollapsePoint.hasClass('show')
|
||||||
|
if(isVisible){
|
||||||
|
sideMenuCollapsePoint.collapse('hide')
|
||||||
|
}else{
|
||||||
|
sideMenuCollapsePoint.collapse('show')
|
||||||
|
}
|
||||||
|
if(!dontSaveChange)dashboardOptions('sideMenuCollapsed',!isVisible ? '0' : 1)
|
||||||
|
}
|
||||||
|
function loadSideMenuCollapseStatus(){
|
||||||
|
var isCollapsed = dashboardOptions().sideMenuCollapsed === 1;
|
||||||
|
if(isCollapsed){
|
||||||
|
sideMenuCollapsePoint.collapse('hide')
|
||||||
|
}else{
|
||||||
|
sideMenuCollapsePoint.collapse('show')
|
||||||
|
}
|
||||||
|
return isCollapsed
|
||||||
|
}
|
||||||
|
function isSideBarMenuCollapsed(){
|
||||||
|
return dashboardOptions().sideMenuCollapsed === 1
|
||||||
|
}
|
||||||
$('#monitors_list_search').keyup(function(){
|
$('#monitors_list_search').keyup(function(){
|
||||||
var monitorBlocks = monitorSideList.find('.monitor_block');
|
var monitorBlocks = monitorSideList.find('.monitor_block');
|
||||||
var searchTerms = $(this).val().toLowerCase().split(' ')
|
var searchTerms = $(this).val().toLowerCase().split(' ')
|
||||||
|
|
@ -140,6 +161,10 @@ onDashboardReady(function(){
|
||||||
drawMonitors()
|
drawMonitors()
|
||||||
fixSideMenuScroll()
|
fixSideMenuScroll()
|
||||||
sortListMonitors()
|
sortListMonitors()
|
||||||
|
loadSideMenuCollapseStatus()
|
||||||
|
$('.toggle-menu-collapse').click(function(){
|
||||||
|
toggleSideMenuCollapse()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
onWebSocketEvent(function(d){
|
onWebSocketEvent(function(d){
|
||||||
switch(d.f){
|
switch(d.f){
|
||||||
|
|
|
||||||
|
|
@ -134,12 +134,6 @@ $('body')
|
||||||
dropdownToggles[keyName] = value
|
dropdownToggles[keyName] = value
|
||||||
dashboardOptions('dropdown_toggle',dropdownToggles)
|
dashboardOptions('dropdown_toggle',dropdownToggles)
|
||||||
})
|
})
|
||||||
.on('click','.logout',function(e){
|
|
||||||
$.get(getApiPrefix() + '/logout/'+$user.ke+'/'+$user.uid,function(data){
|
|
||||||
localStorage.removeItem('ShinobiLogin_' + location.host);
|
|
||||||
location.href = location.href.split('#')[0];
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.on('dblclick','[type="password"],.password_field',function(){
|
.on('dblclick','[type="password"],.password_field',function(){
|
||||||
var _this = $(this)
|
var _this = $(this)
|
||||||
var type = 'password'
|
var type = 'password'
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ $(document).ready(function(){
|
||||||
var notifyColor = 'info'
|
var notifyColor = 'info'
|
||||||
if(data.ok){
|
if(data.ok){
|
||||||
loadedSubAccounts[uid] = null;
|
loadedSubAccounts[uid] = null;
|
||||||
accountTable.find('tr[uid="' + uid + '"]').remove()
|
accountTable.find('[uid="' + uid + '"]').remove()
|
||||||
}else{
|
}else{
|
||||||
notifyTitle = lang.accountActionFailed
|
notifyTitle = lang.accountActionFailed
|
||||||
notifyText = lang.contactAdmin
|
notifyText = lang.contactAdmin
|
||||||
|
|
@ -165,7 +165,7 @@ $(document).ready(function(){
|
||||||
}
|
}
|
||||||
var setPermissionSelectionsToFields = function(uid){
|
var setPermissionSelectionsToFields = function(uid){
|
||||||
var account = loadedSubAccounts[uid]
|
var account = loadedSubAccounts[uid]
|
||||||
var details = $.parseJSON(account.details)
|
var details = safeJsonParse(account.details)
|
||||||
// load values to Account Information : email, password, etc.
|
// load values to Account Information : email, password, etc.
|
||||||
$.each(account,function(n,v){
|
$.each(account,function(n,v){
|
||||||
theWindowForm.find('[name="'+n+'"]').val(v)
|
theWindowForm.find('[name="'+n+'"]').val(v)
|
||||||
|
|
@ -325,6 +325,10 @@ $(document).ready(function(){
|
||||||
$(`#active-user-${user.uid}-${user.cnid}`).remove()
|
$(`#active-user-${user.uid}-${user.cnid}`).remove()
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case'delete_sub_account':
|
||||||
|
var user = d.user
|
||||||
|
accountTable.find(`[uid="${user.uid}"]`).remove()
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
initiateSubAccountPage()
|
initiateSubAccountPage()
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ $(document).ready(function(e){
|
||||||
var downloaderIsChecking = false
|
var downloaderIsChecking = false
|
||||||
var allowKeepChecking = true
|
var allowKeepChecking = true
|
||||||
var selectedFps = 15
|
var selectedFps = 15
|
||||||
|
var currentPlaylistArray = []
|
||||||
|
|
||||||
var openTimelapseWindow = function(monitorId,startDate,endDate){
|
var openTimelapseWindow = function(monitorId,startDate,endDate){
|
||||||
drawTimelapseWindowElements(monitorId,startDate,endDate)
|
drawTimelapseWindowElements(monitorId,startDate,endDate)
|
||||||
|
|
@ -70,7 +71,11 @@ $(document).ready(function(e){
|
||||||
if(!startDate)startDate = dateRange.startDate
|
if(!startDate)startDate = dateRange.startDate
|
||||||
if(!endDate)endDate = dateRange.endDate
|
if(!endDate)endDate = dateRange.endDate
|
||||||
if(!selectedMonitor)selectedMonitor = monitorsList.val()
|
if(!selectedMonitor)selectedMonitor = monitorsList.val()
|
||||||
var queryString = ['start=' + startDate,'end=' + endDate]
|
var queryString = [
|
||||||
|
'start=' + startDate,
|
||||||
|
'end=' + endDate,
|
||||||
|
'noLimit=1'
|
||||||
|
]
|
||||||
var frameIconsHtml = ''
|
var frameIconsHtml = ''
|
||||||
var apiURL = apiBaseUrl + '/timelapse/' + $user.ke + '/' + selectedMonitor
|
var apiURL = apiBaseUrl + '/timelapse/' + $user.ke + '/' + selectedMonitor
|
||||||
$.getJSON(apiURL + '?' + queryString.join('&'),function(data){
|
$.getJSON(apiURL + '?' + queryString.join('&'),function(data){
|
||||||
|
|
@ -82,7 +87,7 @@ $(document).ready(function(e){
|
||||||
$.each(data.reverse(),function(n,fileInfo){
|
$.each(data.reverse(),function(n,fileInfo){
|
||||||
fileInfo.href = apiURL + '/' + fileInfo.filename.split('T')[0] + '/' + fileInfo.filename
|
fileInfo.href = apiURL + '/' + fileInfo.filename.split('T')[0] + '/' + fileInfo.filename
|
||||||
fileInfo.number = n
|
fileInfo.number = n
|
||||||
frameIconsHtml += '<div class="col-md-4 frame-container"><div class="frame" data-filename="' + fileInfo.filename + '" style="background-image:url(\'' + fileInfo.href + '\')"><div class="button-strip"><button type="button" class="btn btn-sm btn-danger delete"><i class="fa fa-trash-o"></i></button></div><div class="shade">' + moment(fileInfo.time).format('YYYY-MM-DD HH:mm:ss') + '</div></div></div>'
|
frameIconsHtml += '<div class="col-md-4 frame-container"><div class="frame" data-filename="' + fileInfo.filename + '" frame-container-unloaded="' + fileInfo.href + '"><div class="button-strip"><button type="button" class="btn btn-sm btn-danger delete"><i class="fa fa-trash-o"></i></button></div><div class="shade">' + moment(fileInfo.time).format('YYYY-MM-DD HH:mm:ss') + '</div></div></div>'
|
||||||
currentPlaylist[fileInfo.filename] = fileInfo
|
currentPlaylist[fileInfo.filename] = fileInfo
|
||||||
})
|
})
|
||||||
currentPlaylistArray = data
|
currentPlaylistArray = data
|
||||||
|
|
@ -90,6 +95,7 @@ $(document).ready(function(e){
|
||||||
frameIcons.find(`.frame:first`).click()
|
frameIcons.find(`.frame:first`).click()
|
||||||
// getLiveStream()
|
// getLiveStream()
|
||||||
resetFilmStripPositions()
|
resetFilmStripPositions()
|
||||||
|
loadVisibleTimelapseFrames()
|
||||||
}else{
|
}else{
|
||||||
frameIconsHtml = lang['No Data']
|
frameIconsHtml = lang['No Data']
|
||||||
frameIcons.html(frameIconsHtml)
|
frameIcons.html(frameIconsHtml)
|
||||||
|
|
@ -193,7 +199,7 @@ $(document).ready(function(e){
|
||||||
title: lang.Delete,
|
title: lang.Delete,
|
||||||
},
|
},
|
||||||
clickCallback: function(){
|
clickCallback: function(){
|
||||||
$.get(frame.href + '/delete',function(response){
|
$.getJSON(frame.href + '/delete',function(response){
|
||||||
if(response.ok){
|
if(response.ok){
|
||||||
el.parent().remove()
|
el.parent().remove()
|
||||||
}
|
}
|
||||||
|
|
@ -231,7 +237,7 @@ $(document).ready(function(e){
|
||||||
setDownloadButtonLabel(lang['Automatic Checking Cancelled'])
|
setDownloadButtonLabel(lang['Automatic Checking Cancelled'])
|
||||||
downloadRecheckTimers[timerId] = setTimeout(function(){
|
downloadRecheckTimers[timerId] = setTimeout(function(){
|
||||||
setDownloadButtonLabel(lang['Build Video'], 'database')
|
setDownloadButtonLabel(lang['Build Video'], 'database')
|
||||||
},5000)
|
},30000)
|
||||||
downloaderIsChecking = false
|
downloaderIsChecking = false
|
||||||
allowKeepChecking = true
|
allowKeepChecking = true
|
||||||
return
|
return
|
||||||
|
|
@ -258,7 +264,7 @@ $(document).ready(function(e){
|
||||||
downloadRecheckTimers[timerId] = setTimeout(function(){
|
downloadRecheckTimers[timerId] = setTimeout(function(){
|
||||||
setDownloadButtonLabel(lang['Please Wait or Click to Stop Checking'], 'spinner fa-pulse')
|
setDownloadButtonLabel(lang['Please Wait or Click to Stop Checking'], 'spinner fa-pulse')
|
||||||
runDownloader()
|
runDownloader()
|
||||||
},5000)
|
},30000)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -269,6 +275,25 @@ $(document).ready(function(e){
|
||||||
playInterval = 1000 / ev.value
|
playInterval = 1000 / ev.value
|
||||||
selectedFps = ev.value + 0
|
selectedFps = ev.value + 0
|
||||||
})
|
})
|
||||||
|
function isElementVisible (el) {
|
||||||
|
const holder = frameIcons[0]
|
||||||
|
const { top, bottom, height } = el.getBoundingClientRect()
|
||||||
|
const holderRect = holder.getBoundingClientRect()
|
||||||
|
|
||||||
|
return top <= holderRect.top
|
||||||
|
? holderRect.top - top <= height
|
||||||
|
: bottom - holderRect.bottom <= height
|
||||||
|
}
|
||||||
|
function loadVisibleTimelapseFrames(){
|
||||||
|
frameIcons.find('[frame-container-unloaded]').each(function(n,v){
|
||||||
|
if(isElementVisible(v)){
|
||||||
|
var el = $(v)
|
||||||
|
var imgSrc = el.attr('frame-container-unloaded')
|
||||||
|
el.removeAttr('frame-container-unloaded').attr('style',`background-image:url(${imgSrc})`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
frameIcons.on('scroll',loadVisibleTimelapseFrames)
|
||||||
$('body').on('click','.open-timelapse-viewer',function(){
|
$('body').on('click','.open-timelapse-viewer',function(){
|
||||||
var el = $(this).parents('[data-mid]')
|
var el = $(this).parents('[data-mid]')
|
||||||
var monitorId = el.attr('data-mid')
|
var monitorId = el.attr('data-mid')
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
function getVideoPlayerTabId(video){
|
||||||
|
return `videoPlayer-${video.mid}-${moment(video.time).format('YYYY-MM-DD-HH-mm-ss')}`
|
||||||
|
}
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
var theBlock = $('#tab-videoPlayer')
|
var theBlock = $('#tab-videoPlayer')
|
||||||
window.createVideoPlayerTab = function(video){
|
window.createVideoPlayerTab = function(video,timeStart){
|
||||||
var newTabId = `videoPlayer-${video.mid}-${moment(video.time).format('YYYY-MM-DD-HH-mm-ss')}`
|
var newTabId = getVideoPlayerTabId(video)
|
||||||
var humanStartTime = formattedTime(video.time,true)
|
var humanStartTime = formattedTime(video.time,true)
|
||||||
var humanEndTime = formattedTime(video.end,true)
|
var humanEndTime = formattedTime(video.end,true)
|
||||||
var tabLabel = `<b>${lang['Video']}</b> : ${loadedMonitors[video.mid].name} : ${formattedTime(video.time,true)}`
|
var tabLabel = `<b>${lang['Video']}</b> : ${loadedMonitors[video.mid].name} : ${formattedTime(video.time,true)}`
|
||||||
|
|
@ -32,40 +35,46 @@ $(document).ready(function(){
|
||||||
})
|
})
|
||||||
eventMatrixHtml += `</div>`
|
eventMatrixHtml += `</div>`
|
||||||
}
|
}
|
||||||
var baseHtml = `<main class="container page-tab tab-videoPlayer" id="tab-${newTabId}" video-id="${video.mid}${video.time}">
|
var baseHtml = `<main class="container page-tab tab-videoPlayer" id="tab-${newTabId}" video-id="${video.mid}${video.time}" data-mid="${video.mid}" data-time="${video.time}">
|
||||||
<div class="my-3 ${definitions.Theme.isDark ? 'bg-dark text-white' : 'bg-light text-dark'} rounded shadow-sm">
|
<div class="mt-3 ${definitions.Theme.isDark ? 'bg-dark text-white' : 'bg-light text-dark'} rounded shadow-sm" style="overflow:hidden">
|
||||||
<div class="p-3">
|
<div class="d-flex flex-row">
|
||||||
<h6 class="video-title border-bottom-dotted border-bottom-dark pb-2 mb-0">${tabLabel}</h6>
|
<div class="flex-grow-1">
|
||||||
|
<h6 class="video-title border-bottom-dotted border-bottom-dark p-3 mb-0">${tabLabel}</h6>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-default btn-sm delete-tab-dynamic"><i class="fa fa-times"></i></a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="position: relative">
|
<div style="position: relative">
|
||||||
<div class="tab-videoPlayer-event-objects" style="position: absolute;width: 100%; height: 100%; z-index: 10"></div>
|
<div class="tab-videoPlayer-event-objects" style="position: absolute;width: 100%; height: 100%; z-index: 10"></div>
|
||||||
<video class="tab-videoPlayer-video-element" controls autoplay src="${videoUrl}"></video>
|
<video class="tab-videoPlayer-video-element" controls autoplay src="${videoUrl}"></video>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-3">
|
<div class="d-flex flex-row">
|
||||||
<div class="d-block">
|
<div class="d-block p-3">
|
||||||
<a class="btn btn-sm btn-success" download href="${videoUrl}"><i class="fa fa-download"></i> ${lang.Download}</a>
|
<b class="flex-grow-1">${lang.Started}</b>
|
||||||
</div>
|
<div class="video-time">${humanStartTime}</div>
|
||||||
<div class="d-block">
|
</div>
|
||||||
<b class="flex-grow-1">${lang.Started}</b>
|
<div class="d-block p-3">
|
||||||
<div class="video-time">${humanStartTime}</div>
|
<b class="flex-grow-1">${lang.Ended}</b>
|
||||||
</div>
|
<div class="video-end">${humanEndTime}</div>
|
||||||
<div class="d-block">
|
</div>
|
||||||
<b class="flex-grow-1">${lang.Ended}</b>
|
</div>
|
||||||
<div class="video-end">${humanEndTime}</div>
|
<div class="mb-3">
|
||||||
</div>
|
${eventMatrixHtml}
|
||||||
<div class="d-block">
|
</div>
|
||||||
${eventMatrixHtml}
|
<div class="d-flex flex-row">
|
||||||
</div>
|
<div class="flex-grow-1 bg-gradient-blue">
|
||||||
<small class="d-block text-end mt-3">
|
<a class="btn btn-block px-1 py-3 ${definitions.Theme.isDark ? 'text-white' : 'text-dark'}" download href="${videoUrl}"><i class="fa fa-download"></i> ${lang.Download}</a>
|
||||||
<a class="go-back btn badge">${lang['Back']}</a>
|
</div>
|
||||||
</small>
|
<div class="flex-grow-1 bg-gradient-orange">
|
||||||
|
<a class="btn btn-block px-1 py-3 delete-video ${definitions.Theme.isDark ? 'text-white' : 'text-dark'}"><i class="fa fa-trash-o"></i> ${lang.Delete}</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>`
|
</main>`
|
||||||
var tabCreateResponse = createNewTab(newTabId,tabLabel,baseHtml,{},null,'videoPlayer')
|
var tabCreateResponse = createNewTab(newTabId,tabLabel,baseHtml,{},null,'videoPlayer')
|
||||||
console.log(tabCreateResponse)
|
var videoElement = tabCreateResponse.theTab.find('.tab-videoPlayer-video-element')[0]
|
||||||
if(!tabCreateResponse.existAlready){
|
if(!tabCreateResponse.existAlready){
|
||||||
var videoElement = tabCreateResponse.theTab.find('.tab-videoPlayer-video-element')[0]
|
|
||||||
var videoObjectContainer = tabCreateResponse.theTab.find('.tab-videoPlayer-event-objects')
|
var videoObjectContainer = tabCreateResponse.theTab.find('.tab-videoPlayer-event-objects')
|
||||||
var videoHeight = videoObjectContainer.height()
|
var videoHeight = videoObjectContainer.height()
|
||||||
var videoWidth = videoObjectContainer.width()
|
var videoWidth = videoObjectContainer.width()
|
||||||
|
|
@ -83,6 +92,7 @@ $(document).ready(function(){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(timeStart)videoElement.currentTime = timeStart;
|
||||||
}
|
}
|
||||||
window.closeVideoPlayer = function(tabId){
|
window.closeVideoPlayer = function(tabId){
|
||||||
console.log('closeVideoPlayer')
|
console.log('closeVideoPlayer')
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,38 @@
|
||||||
var loadedVideosInMemory = {}
|
var loadedVideosInMemory = {}
|
||||||
|
var loadedFramesMemory = {}
|
||||||
|
var loadedFramesMemoryTimeout = {}
|
||||||
|
function getLocalTimelapseImageLink(imageUrl){
|
||||||
|
if(loadedFramesMemory[imageUrl]){
|
||||||
|
return loadedFramesMemory[imageUrl]
|
||||||
|
}else{
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
fetch(imageUrl)
|
||||||
|
.then(res => res.blob()) // Gets the response and returns it as a blob
|
||||||
|
.then(blob => {
|
||||||
|
var objectURL = URL.createObjectURL(blob);
|
||||||
|
loadedFramesMemory[imageUrl] = objectURL
|
||||||
|
clearTimeout(loadedFramesMemoryTimeout[imageUrl])
|
||||||
|
loadedFramesMemoryTimeout[imageUrl] = setTimeout(function(){
|
||||||
|
URL.revokeObjectURL(objectURL)
|
||||||
|
delete(loadedFramesMemory[imageUrl])
|
||||||
|
delete(loadedFramesMemoryTimeout[imageUrl])
|
||||||
|
},1000 * 60 * 10)
|
||||||
|
resolve(objectURL)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function preloadAllTimelapseFramesToMemoryFromVideoList(videos){
|
||||||
|
for (let i = 0; i < videos.length; i++) {
|
||||||
|
var video = videos[i]
|
||||||
|
for (let ii = 0; ii < video.timelapseFrames.length; ii++) {
|
||||||
|
var frame = video.timelapseFrames[ii]
|
||||||
|
// console.log ('Loading... ',frame.href)
|
||||||
|
await getLocalTimelapseImageLink(frame.href)
|
||||||
|
// console.log ('Loaded! ',frame.href)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
function createVideoLinks(video){
|
function createVideoLinks(video){
|
||||||
var details = safeJsonParse(video.details)
|
var details = safeJsonParse(video.details)
|
||||||
var queryString = []
|
var queryString = []
|
||||||
|
|
@ -32,7 +66,7 @@ function createVideoLinks(video){
|
||||||
video.details = details
|
video.details = details
|
||||||
return video
|
return video
|
||||||
}
|
}
|
||||||
function applyEventListToVideos(videos,events){
|
function applyDataListToVideos(videos,events,keyName,reverseList){
|
||||||
var updatedVideos = videos.concat([])
|
var updatedVideos = videos.concat([])
|
||||||
var currentEvents = events.concat([])
|
var currentEvents = events.concat([])
|
||||||
updatedVideos.forEach(function(video){
|
updatedVideos.forEach(function(video){
|
||||||
|
|
@ -41,48 +75,182 @@ function applyEventListToVideos(videos,events){
|
||||||
var startTime = new Date(video.time)
|
var startTime = new Date(video.time)
|
||||||
var endTime = new Date(video.end)
|
var endTime = new Date(video.end)
|
||||||
var eventTime = new Date(theEvent.time)
|
var eventTime = new Date(theEvent.time)
|
||||||
if(eventTime >= startTime && eventTime <= endTime){
|
if(theEvent.mid === video.mid && eventTime >= startTime && eventTime <= endTime){
|
||||||
videoEvents.push(theEvent)
|
videoEvents.push(theEvent)
|
||||||
currentEvents.splice(index, 1)
|
currentEvents.splice(index, 1)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
video.events = videoEvents
|
if(reverseList)videoEvents = videoEvents.reverse()
|
||||||
|
video[keyName || 'events'] = videoEvents
|
||||||
})
|
})
|
||||||
return updatedVideos
|
return updatedVideos
|
||||||
}
|
}
|
||||||
function createVideoRow(row,classOverride){
|
function applyTimelapseFramesListToVideos(videos,events,keyName,reverseList){
|
||||||
var possibleEventFrames = ''
|
var thisApiPrefix = getApiPrefix() + '/timelapse/' + $user.ke + '/'
|
||||||
var hasRows = row.events && row.events.length > 0
|
var newVideos = applyDataListToVideos(videos,events,keyName,reverseList)
|
||||||
if(hasRows){
|
newVideos.forEach(function(video){
|
||||||
var eventMatrixHtml = ``
|
video.timelapseFrames.forEach(function(row){
|
||||||
var objectsFound = {}
|
var apiURL = thisApiPrefix + row.mid
|
||||||
eventMatrixHtml += `
|
row.href = libURL + apiURL + '/' + row.filename.split('T')[0] + '/' + row.filename
|
||||||
<table class="table table-striped mb-0">
|
})
|
||||||
<tr>
|
})
|
||||||
<th scope="col" class="${definitions.Theme.isDark ? 'text-white' : ''} text-epic">${lang.Events}</th>
|
return newVideos
|
||||||
<th scope="col" class="text-end"><span class="badge bg-light text-dark rounded-pill">${row.events.length}</span></th>
|
}
|
||||||
</tr>`
|
function getFrameOnVideoRow(percentageInward,video){
|
||||||
$.each(([]).concat(row.events).splice(0,11),function(n,theEvent){
|
var startTime = new Date(video.time)
|
||||||
var imagePath = `${formattedTimeForFilename(theEvent.time,false,'YYYY-MM-DD')}/${formattedTimeForFilename(theEvent.time,false,'YYYY-MM-DDTHH-mm-ss')}.jpg`
|
var endTime = new Date(video.end)
|
||||||
possibleEventFrames += `<div class="col-4 mb-2"><img class="rounded pop-image cursor-pointer" style="max-width:100%;" src="${getApiPrefix('timelapse')}/${theEvent.mid}/${imagePath}" onerror="$(this).parent().remove()"></div>`
|
var timeDifference = endTime - startTime
|
||||||
})
|
var timeInward = timeDifference / (100 / percentageInward)
|
||||||
$.each(row.events,function(n,theEvent){
|
var timeAdded = new Date(startTime.getTime() + timeInward) // ms
|
||||||
$.each(theEvent.details.matrices,function(n,matrix){
|
var foundFrame = video.timelapseFrames.find(function(row){
|
||||||
if(!objectsFound[matrix.tag])objectsFound[matrix.tag] = 1
|
return new Date(row.time) > timeAdded
|
||||||
++objectsFound[matrix.tag]
|
});
|
||||||
|
return {
|
||||||
|
timeInward: timeInward,
|
||||||
|
foundFrame: foundFrame,
|
||||||
|
timeAdded: timeAdded,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getVideoFromDay(percentageInward,videos){
|
||||||
|
var startTime = new Date(videos[0].time)
|
||||||
|
var endTime = new Date(videos[videos.length - 1].end)
|
||||||
|
var timeDifference = endTime - startTime
|
||||||
|
var timeInward = timeDifference / (100 / percentageInward)
|
||||||
|
var timeAdded = new Date(startTime.getTime() + timeInward) // ms
|
||||||
|
var foundVideo = ([]).concat(videos).reverse().find(function(row){
|
||||||
|
return new Date(timeAdded - 1000) >= new Date(row.time)
|
||||||
|
});
|
||||||
|
return foundVideo
|
||||||
|
}
|
||||||
|
function bindFrameFindingByMouseMove(createdCardCarrier,video){
|
||||||
|
var createdCardElement = createdCardCarrier.find('.card').first()
|
||||||
|
var timeImg = createdCardElement.find('.video-time-img')
|
||||||
|
var timeStrip = createdCardElement.find('.video-time-strip')
|
||||||
|
var timeNeedleSeeker = createdCardElement.find('.video-time-needle-seeker')
|
||||||
|
if(video.timelapseFrames.length > 0){
|
||||||
|
createdCardElement.on('mousemove',function(evt){
|
||||||
|
var offest = createdCardElement.offset()
|
||||||
|
var elementWidth = createdCardElement.width() + 2
|
||||||
|
var amountMoved = evt.pageX - offest.left
|
||||||
|
var percentMoved = amountMoved / elementWidth * 100
|
||||||
|
percentMoved = percentMoved > 100 ? 100 : percentMoved < 0 ? 0 : percentMoved
|
||||||
|
var frameFound = getFrameOnVideoRow(percentMoved,video).frameFound
|
||||||
|
if(frameFound){
|
||||||
|
timeImg.css('background-image',`url(${frameFound.href})`)
|
||||||
|
}
|
||||||
|
timeNeedleSeeker.css('left',`${amountMoved}px`)
|
||||||
|
})
|
||||||
|
timeImg.css('background-image',`url(${getFrameOnVideoRow(1,video).frameFound.href})`)
|
||||||
|
}else{
|
||||||
|
if(video.events.length === 0){
|
||||||
|
timeStrip.hide()
|
||||||
|
}else{
|
||||||
|
var eventMatrixHtml = ``
|
||||||
|
var objectsFound = {}
|
||||||
|
eventMatrixHtml += `
|
||||||
|
<table class="table table-striped mb-0">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="${definitions.Theme.isDark ? 'text-white' : ''} text-epic">${lang.Events}</th>
|
||||||
|
<th scope="col" class="text-end"><span class="badge bg-light text-dark rounded-pill">${video.events.length}</span></th>
|
||||||
|
</tr>`
|
||||||
|
$.each(([]).concat(video.events).splice(0,11),function(n,theEvent){
|
||||||
|
var imagePath = `${formattedTimeForFilename(theEvent.time,false,'YYYY-MM-DD')}/${formattedTimeForFilename(theEvent.time,false,'YYYY-MM-DDTHH-mm-ss')}.jpg`
|
||||||
|
possibleEventFrames += `<div class="col-4 mb-2"><img class="rounded pop-image cursor-pointer" style="max-width:100%;" src="${getApiPrefix('timelapse')}/${theEvent.mid}/${imagePath}" onerror="$(this).parent().remove()"></div>`
|
||||||
})
|
})
|
||||||
})
|
$.each(video.events,function(n,theEvent){
|
||||||
$.each(objectsFound,function(tag,count){
|
$.each(theEvent.details.matrices,function(n,matrix){
|
||||||
eventMatrixHtml += `<tr>
|
if(!objectsFound[matrix.tag])objectsFound[matrix.tag] = 1
|
||||||
<td class="${definitions.Theme.isDark ? 'text-white' : ''}" style="text-transform:capitalize">${tag}</td>
|
++objectsFound[matrix.tag]
|
||||||
<td class="text-end"><span class="badge ${definitions.Theme.isDark ? 'bg-light text-dark' : 'bg-dark text-white'} rounded-pill">${count}</span></td>
|
})
|
||||||
</tr>`
|
})
|
||||||
})
|
$.each(objectsFound,function(tag,count){
|
||||||
eventMatrixHtml += `</table>`
|
eventMatrixHtml += `<tr>
|
||||||
|
<td class="${definitions.Theme.isDark ? 'text-white' : ''}" style="text-transform:capitalize">${tag}</td>
|
||||||
|
<td class="text-end"><span class="badge badge-dark text-white rounded-pill">${count}</span></td>
|
||||||
|
</tr>`
|
||||||
|
})
|
||||||
|
eventMatrixHtml += `</table>`
|
||||||
|
timeStrip.append(eventMatrixHtml)
|
||||||
|
}
|
||||||
|
timeImg.css('min-height',`auto`).addClass('video-time-no-img')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function bindFrameFindingByMouseMoveForDay(createdCardCarrier,dayKey,videos){
|
||||||
|
var createdCardElement = createdCardCarrier.find('.card').first()
|
||||||
|
var timeImg = createdCardElement.find('.video-time-img')
|
||||||
|
var timeStrip = createdCardElement.find('.video-time-strip')
|
||||||
|
var timeNeedleSeeker = createdCardElement.find('.video-time-needle-seeker')
|
||||||
|
var videoLabel = createdCardElement.find('.video-time-label-selected-video')
|
||||||
|
var dayStart = videos[0].time
|
||||||
|
var dayEnd = videos[videos.length - 1].end
|
||||||
|
var hasFrames = false
|
||||||
|
$.each(videos,function(day,video){
|
||||||
|
if(video.timelapseFrames.length > 0){
|
||||||
|
hasFrames = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if(hasFrames){
|
||||||
|
var videoSlices = createdCardElement.find('.video-day-slice')
|
||||||
|
var videoTimeLabel = createdCardElement.find('.video-time-label')
|
||||||
|
var currentlySelected = null
|
||||||
|
var currentlySelectedFrame = null
|
||||||
|
createdCardElement.on('mousemove',function(evt){
|
||||||
|
var offest = createdCardElement.offset()
|
||||||
|
var elementWidth = createdCardElement.width() + 2
|
||||||
|
var amountMoved = evt.pageX - offest.left
|
||||||
|
var percentMoved = amountMoved / elementWidth * 100
|
||||||
|
percentMoved = percentMoved > 100 ? 100 : percentMoved < 0 ? 0 : percentMoved
|
||||||
|
var videoFound = getVideoFromDay(percentMoved,videos)
|
||||||
|
createdCardElement.find(`[data-time]`).css('background-color','')
|
||||||
|
if(videoFound){
|
||||||
|
var videoSlice = createdCardElement.find(`[data-time="${videoFound.time}"]`).css('background-color','rgba(0,0,0,0.3)')
|
||||||
|
var videoSliceOffest = videoSlice.offset()
|
||||||
|
var videoSliceElementWidth = videoSlice.width()
|
||||||
|
var videoSliceAmountMoved = evt.pageX - videoSliceOffest.left
|
||||||
|
var videoSlicePercentMoved = videoSliceAmountMoved / videoSliceElementWidth * 100
|
||||||
|
videoSlicePercentMoved = videoSlicePercentMoved > 100 ? 100 : videoSlicePercentMoved < 0 ? 0 : videoSlicePercentMoved
|
||||||
|
var result = getFrameOnVideoRow(videoSlicePercentMoved,videoFound)
|
||||||
|
var frameFound = result.foundFrame
|
||||||
|
videoTimeLabel.text(formattedTime(result.timeAdded))
|
||||||
|
if(frameFound){
|
||||||
|
currentlySelectedFrame = Object.assign({},frameFound)
|
||||||
|
setTimeout(async function(){
|
||||||
|
var frameUrl = await getLocalTimelapseImageLink(frameFound.href)
|
||||||
|
if(currentlySelectedFrame.time === frameFound.time)timeImg.css('background-image',`url(${frameUrl})`);
|
||||||
|
},1)
|
||||||
|
}
|
||||||
|
if(currentlySelected && currentlySelected.time !== videoFound.time){
|
||||||
|
timeNeedleSeeker.attr('video-time-seeked-video-position',videoFound.time)
|
||||||
|
videoLabel.text(videoFound.time)
|
||||||
|
}
|
||||||
|
currentlySelected = Object.assign({},videoFound)
|
||||||
|
timeNeedleSeeker.attr('video-slice-seeked',result.timeInward)
|
||||||
|
}
|
||||||
|
timeNeedleSeeker.css('left',`${percentMoved}%`)
|
||||||
|
})
|
||||||
|
createdCardElement.trigger('mousemove')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function getPercentOfTimePositionFromVideo(video,theEvent){
|
||||||
|
var startTime = new Date(video.time)
|
||||||
|
var endTime = new Date(video.end)
|
||||||
|
var eventTime = new Date(theEvent.time)
|
||||||
|
var rangeMax = endTime - startTime
|
||||||
|
var eventMs = eventTime - startTime
|
||||||
|
var percentChanged = eventMs / rangeMax * 100
|
||||||
|
return percentChanged
|
||||||
|
}
|
||||||
|
function createVideoRow(row,classOverride){
|
||||||
|
var eventMatrixHtml = ``
|
||||||
|
if(row.events && row.events.length > 0){
|
||||||
|
$.each(row.events,function(n,theEvent){
|
||||||
|
var leftPercent = getPercentOfTimePositionFromVideo(row,theEvent)
|
||||||
|
eventMatrixHtml += `<div class="video-time-needle video-time-needle-event" style="left:${leftPercent}%"></div>`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
var videoEndpoint = getLocation() + '/' + $user.auth_token + '/videos/' + $user.ke + '/' + row.mid + '/' + row.filename
|
var videoEndpoint = getLocation() + '/' + $user.auth_token + '/videos/' + $user.ke + '/' + row.mid + '/' + row.filename
|
||||||
return `
|
return `
|
||||||
<div class="video-row ${classOverride ? classOverride : `col-md-12 col-lg-6 mb-3`} search-row" data-mid="${row.mid}" data-time="${row.time}">
|
<div class="video-row ${classOverride ? classOverride : `col-md-12 col-lg-6 mb-3`} search-row" data-mid="${row.mid}" data-time="${row.time}" data-time-formed="${new Date(row.time)}">
|
||||||
<div class="card shadow-lg px-0 btn-default">
|
<div class="card shadow-lg px-0 btn-default">
|
||||||
<div class="card-header d-flex flex-row">
|
<div class="card-header d-flex flex-row">
|
||||||
<div class="flex-grow-1 ${definitions.Theme.isDark ? 'text-white' : ''}">
|
<div class="flex-grow-1 ${definitions.Theme.isDark ? 'text-white' : ''}">
|
||||||
|
|
@ -94,29 +262,95 @@ function createVideoRow(row,classOverride){
|
||||||
<a class="badge btn btn-danger delete-video" title="${lang['Delete']}"><i class="fa fa-trash-o"></i></a>
|
<a class="badge btn btn-danger delete-video" title="${lang['Delete']}"><i class="fa fa-trash-o"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="video-time-img">
|
||||||
<div title="${row.time}" class="d-flex flex-row">
|
<div class="card-body">
|
||||||
<div class="flex-grow-1">
|
<div title="${row.time}" class="d-flex flex-row">
|
||||||
${moment(row.time).fromNow()}
|
<div class="flex-grow-1">
|
||||||
|
${moment(row.time).fromNow()}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<small class="text-muted">~${durationBetweenTimes(row.time,row.end)} ${lang.Minutes}</small>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div title="${row.time}" class="d-flex flex-row border-bottom-dotted border-bottom-dark mb-2">
|
||||||
<small class="text-muted">~${durationBetweenTimes(row.time,row.end)} ${lang.Minutes}</small>
|
<div>
|
||||||
|
<div title="${row.time}"><small class="text-muted">${lang.Started} : ${formattedTime(row.time,true)}</small></div>
|
||||||
|
<div title="${row.end}"><small class="text-muted">${lang.Ended} : ${formattedTime(row.end,true)}</small></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div title="${row.time}" class="d-flex flex-row border-bottom-dotted border-bottom-dark mb-2">
|
|
||||||
<div>
|
|
||||||
<div title="${row.time}"><small class="text-muted">${lang.Started} : ${formattedTime(row.time,true)}</small></div>
|
|
||||||
<div title="${row.end}"><small class="text-muted">${lang.Ended} : ${formattedTime(row.end,true)}</small></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">${possibleEventFrames}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer p-0">
|
<div class="video-time-strip card-footer p-0">
|
||||||
${hasRows ? eventMatrixHtml : `<div class="p-3"><small class="text-muted">${lang['No Events found for this video']}</small></div>`}
|
${eventMatrixHtml}
|
||||||
|
<div class="video-time-needle video-time-needle-seeker" style="z-index: 2"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</div>`
|
||||||
}
|
}
|
||||||
|
function sortVideosByDays(videos){
|
||||||
|
var days = {}
|
||||||
|
videos.forEach(function(video){
|
||||||
|
var videoTime = new Date(video.time)
|
||||||
|
var theDayKey = `${videoTime.getDate()}-${videoTime.getMonth()}-${videoTime.getFullYear()}`
|
||||||
|
if(!days[video.mid])days[video.mid] = {};
|
||||||
|
if(!days[video.mid][theDayKey])days[video.mid][theDayKey] = [];
|
||||||
|
days[video.mid][theDayKey].push(video)
|
||||||
|
})
|
||||||
|
return days
|
||||||
|
}
|
||||||
|
function getVideoPercentWidthForDay(row,videos){
|
||||||
|
var startTime = new Date(row.time)
|
||||||
|
var endTime = new Date(row.end)
|
||||||
|
var timeDifference = endTime - startTime
|
||||||
|
var stripStartTime = new Date(videos[0].time)
|
||||||
|
var stripEndTime = new Date(videos[videos.length - 1].end)
|
||||||
|
var stripTimeDifference = stripEndTime - stripStartTime
|
||||||
|
var percent = (timeDifference / stripTimeDifference) * 100
|
||||||
|
return percent
|
||||||
|
}
|
||||||
|
function createDayCard(videos,dayKey,monitorId,classOverride){
|
||||||
|
var html = ''
|
||||||
|
var eventMatrixHtml = ``
|
||||||
|
$.each(videos,function(n,row){
|
||||||
|
eventMatrixHtml += `<div class="video-day-slice" data-mid="${row.mid}" data-time="${row.time}" style="width:${getVideoPercentWidthForDay(row,videos)}%;position:relative">`
|
||||||
|
if(row.events && row.events.length > 0){
|
||||||
|
$.each(row.events,function(n,theEvent){
|
||||||
|
var leftPercent = getPercentOfTimePositionFromVideo(row,theEvent)
|
||||||
|
eventMatrixHtml += `<div class="video-time-needle video-time-needle-event" style="left:${leftPercent}%"></div>`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
eventMatrixHtml += `</div>`
|
||||||
|
})
|
||||||
|
html += `
|
||||||
|
<div class="video-row ${classOverride ? classOverride : `col-md-12 col-lg-6 mb-3`} search-row">
|
||||||
|
<div class="card shadow-sm px-0 ${definitions.Theme.isDark ? 'bg-dark' : 'bg-light'}" style="border-radius: 10px;overflow: hidden;">
|
||||||
|
<div class="video-time-header p-3">
|
||||||
|
<div class="d-flex flex-row ${definitions.Theme.isDark ? 'text-white' : ''}">
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<b>${loadedMonitors[monitorId] ? loadedMonitors[monitorId].name : monitorId}</b>
|
||||||
|
</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<span class="badge badge-sm badge-primary">${dayKey}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="${definitions.Theme.isDark ? 'text-white' : ''}">
|
||||||
|
<span class="video-time-label">${formattedTime(videos[0].time)} to ${formattedTime(videos[videos.length - 1].end)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="${definitions.Theme.isDark ? 'text-white' : ''}">
|
||||||
|
<span class="video-time-label-selected-video"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="video-time-img" style="background-image: url(${videos[0].timelapseFrames[0] ? videos[0].timelapseFrames[0].href : ''})">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="video-time-strip card-footer p-0">
|
||||||
|
<div class="flex-row d-flex" style="height:30px">${eventMatrixHtml}</div>
|
||||||
|
<div class="video-time-needle video-time-needle-seeker" data-mid="${videos[0].mid}" style="z-index: 2"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`
|
||||||
|
return html
|
||||||
|
}
|
||||||
function drawVideoRowsToList(targetElement,rows){
|
function drawVideoRowsToList(targetElement,rows){
|
||||||
var theVideoList = $(targetElement)
|
var theVideoList = $(targetElement)
|
||||||
theVideoList.empty()
|
theVideoList.empty()
|
||||||
|
|
@ -146,14 +380,17 @@ function getVideos(options,callback){
|
||||||
eventEndTime = formattedTimeForFilename(options.endDate,false)
|
eventEndTime = formattedTimeForFilename(options.endDate,false)
|
||||||
requestQueries.push(`end=${eventEndTime}`)
|
requestQueries.push(`end=${eventEndTime}`)
|
||||||
}
|
}
|
||||||
$.get(`${getApiPrefix(`videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`limit=${limit}`]).join('&')}`,function(data){
|
$.getJSON(`${getApiPrefix(`videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(data){
|
||||||
var videos = data.videos
|
var videos = data.videos
|
||||||
$.get(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.join('&')}`,function(eventData){
|
$.getJSON(`${getApiPrefix(`timelapse`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(timelapseFrames){
|
||||||
var newVideos = applyEventListToVideos(videos,eventData)
|
$.getJSON(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(eventData){
|
||||||
$.each(newVideos,function(n,video){
|
var newVideos = applyDataListToVideos(videos,eventData)
|
||||||
loadVideoData(video)
|
newVideos = applyTimelapseFramesListToVideos(newVideos,timelapseFrames,'timelapseFrames',true)
|
||||||
|
$.each(newVideos,function(n,video){
|
||||||
|
loadVideoData(video)
|
||||||
|
})
|
||||||
|
callback({videos: newVideos})
|
||||||
})
|
})
|
||||||
callback({videos: newVideos})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -177,10 +414,30 @@ function getEvents(options,callback){
|
||||||
if(options.onlyCount){
|
if(options.onlyCount){
|
||||||
requestQueries.push(`onlyCount=1`)
|
requestQueries.push(`onlyCount=1`)
|
||||||
}
|
}
|
||||||
$.get(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.join('&')}`,function(eventData){
|
$.getJSON(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.join('&')}`,function(eventData){
|
||||||
callback(eventData)
|
callback(eventData)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
onWebSocketEvent(function(d){
|
||||||
|
switch(d.f){
|
||||||
|
case'video_delete':
|
||||||
|
$('[file="'+d.filename+'"][mid="'+d.mid+'"]:not(.modal)').remove();
|
||||||
|
$('[data-file="'+d.filename+'"][data-mid="'+d.mid+'"]:not(.modal)').remove();
|
||||||
|
$('[data-time-formed="'+(new Date(d.time))+'"][data-mid="'+d.mid+'"]:not(.modal)').remove();
|
||||||
|
var videoPlayerId = getVideoPlayerTabId(d)
|
||||||
|
deleteTab(videoPlayerId)
|
||||||
|
// if($.powerVideoWindow.currentDataObject&&$.powerVideoWindow.currentDataObject[d.filename]){
|
||||||
|
// delete($.timelapse.currentVideos[$.powerVideoWindow.currentDataObject[d.filename].position])
|
||||||
|
// $.powerVideoWindow.drawTimeline(false)
|
||||||
|
// }
|
||||||
|
// if($.timelapse.currentVideos&&$.timelapse.currentVideos[d.filename]){
|
||||||
|
// delete($.timelapse.currentVideosArray.videos[$.timelapse.currentVideos[d.filename].position])
|
||||||
|
// $.timelapse.drawTimeline(false)
|
||||||
|
// }
|
||||||
|
// if($.vidview.loadedVideos && $.vidview.loadedVideos[d.filename])delete($.vidview.loadedVideos[d.filename])
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
})
|
||||||
$(document).ready(function(){
|
$(document).ready(function(){
|
||||||
$('body')
|
$('body')
|
||||||
.on('click','.open-video',function(){
|
.on('click','.open-video',function(){
|
||||||
|
|
@ -190,6 +447,15 @@ $(document).ready(function(){
|
||||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}`]
|
var video = loadedVideosInMemory[`${monitorId}${videoTime}`]
|
||||||
createVideoPlayerTab(video)
|
createVideoPlayerTab(video)
|
||||||
})
|
})
|
||||||
|
.on('click','[video-time-seeked-video-position]',function(){
|
||||||
|
var el = $(this)
|
||||||
|
var monitorId = el.attr('data-mid')
|
||||||
|
var videoTime = el.attr('video-time-seeked-video-position')
|
||||||
|
var timeInward = (parseInt(el.attr('video-slice-seeked')) / 1000) - 2
|
||||||
|
var video = loadedVideosInMemory[`${monitorId}${videoTime}`]
|
||||||
|
timeInward = timeInward < 0 ? 0 : timeInward
|
||||||
|
createVideoPlayerTab(video,timeInward)
|
||||||
|
})
|
||||||
.on('click','.delete-video',function(){
|
.on('click','.delete-video',function(){
|
||||||
var el = $(this).parents('[data-mid]')
|
var el = $(this).parents('[data-mid]')
|
||||||
var monitorId = el.attr('data-mid')
|
var monitorId = el.attr('data-mid')
|
||||||
|
|
@ -206,7 +472,7 @@ $(document).ready(function(){
|
||||||
class: 'btn-danger btn-sm'
|
class: 'btn-danger btn-sm'
|
||||||
},
|
},
|
||||||
clickCallback: function(){
|
clickCallback: function(){
|
||||||
$.get(videoEndpoint + '/delete',function(data){
|
$.getJSON(videoEndpoint + '/delete',function(data){
|
||||||
if(data.ok){
|
if(data.ok){
|
||||||
console.log('Video Deleted')
|
console.log('Video Deleted')
|
||||||
}else{
|
}else{
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ var mainSocket = {}
|
||||||
var websocketPath = checkCorrectPathEnding(location.pathname) + 'socket.io'
|
var websocketPath = checkCorrectPathEnding(location.pathname) + 'socket.io'
|
||||||
var websocketQuery = {}
|
var websocketQuery = {}
|
||||||
if(location.search === '?p2p=1'){
|
if(location.search === '?p2p=1'){
|
||||||
|
window.machineId = location.pathname.split('/')[2]
|
||||||
websocketPath = '/socket.io'
|
websocketPath = '/socket.io'
|
||||||
websocketQuery.machineId = machineId
|
websocketQuery.machineId = machineId
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ $(document).ready(function(){
|
||||||
var selectedServer = p2pServerList[currentlyRegisteredP2PServer]
|
var selectedServer = p2pServerList[currentlyRegisteredP2PServer]
|
||||||
console.log(selectedServer,currentlySelectedP2PServerId,p2pServerList)
|
console.log(selectedServer,currentlySelectedP2PServerId,p2pServerList)
|
||||||
if(selectedServer && selectedServer.host){
|
if(selectedServer && selectedServer.host){
|
||||||
var href = `http://${selectedServer.host}:${selectedServer.webPort}/s/${apiKey}?p2p=1`
|
var href = `http://${selectedServer.host}:${selectedServer.webPort}/s/${apiKey}/?p2p=1`
|
||||||
var win = window.open(href, '_blank');
|
var win = window.open(href, '_blank');
|
||||||
win.focus();
|
win.focus();
|
||||||
}else{
|
}else{
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
var adminApiPrefix = location.search === '?p2p=1' ? location.pathname + '/' : "<%=originalURL%><%=config.webPaths.adminApiPrefix%>"
|
var adminApiPrefix = location.search === '?p2p=1' ? location.pathname : "<%=originalURL%><%=config.webPaths.adminApiPrefix%>"
|
||||||
</script>
|
</script>
|
||||||
<% include blocks/confirm.ejs %>
|
<% include blocks/confirm.ejs %>
|
||||||
<% include blocks/subpermissions.ejs %>
|
<% include blocks/subpermissions.ejs %>
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
<script src="<%-window.libURL%>libs/js/livestamp.min.js"></script>
|
<script src="<%-window.libURL%>libs/js/livestamp.min.js"></script>
|
||||||
<script src="<%-window.libURL%>libs/js/poseidon.js"></script>
|
<script src="<%-window.libURL%>libs/js/poseidon.js"></script>
|
||||||
<script src="<%-window.libURL%>assets/vendor/bootstrap5/js/bootstrap.bundle.min.js"></script>
|
<script src="<%-window.libURL%>assets/vendor/bootstrap5/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script>
|
<!-- <script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script> -->
|
||||||
<script src="<%-window.libURL%>libs/js/daterangepicker.js"></script>
|
<script src="<%-window.libURL%>libs/js/daterangepicker.js"></script>
|
||||||
<script src="<%-window.libURL%>libs/js/placeholder.js"></script>
|
<script src="<%-window.libURL%>libs/js/placeholder.js"></script>
|
||||||
<script src="<%-window.libURL%>libs/js/lodash.min.js"></script>
|
<script src="<%-window.libURL%>libs/js/lodash.min.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<script>definitions = <%- JSON.stringify(define) %></script>
|
<script>definitions = <%- JSON.stringify(define) %></script>
|
||||||
<script>addStorage = <%- JSON.stringify(addStorage) %>;</script>
|
<script>addStorage = <%- JSON.stringify(addStorage) %>;</script>
|
||||||
<script>logoLocation196x196 = `<%- window.libURL + config.logoLocation196x196 %>`;</script>
|
<script>logoLocation196x196 = `<%- window.libURL + config.logoLocation196x196 %>`;</script>
|
||||||
<script>libURL = location.search === '?p2p=1' ? location.pathname + '/' : '<%-window.libURL%>';</script>
|
<script>libURL = location.search === '?p2p=1' ? location.pathname : '<%-window.libURL%>';</script>
|
||||||
<script>HWAccelChoices = [
|
<script>HWAccelChoices = [
|
||||||
<% if(config.availableHWAccels) {
|
<% if(config.availableHWAccels) {
|
||||||
var methods = {
|
var methods = {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
<% window.additionalJsScripts = []; %>
|
<% window.additionalJsScripts = []; %>
|
||||||
<%
|
<%
|
||||||
|
if(config.baseURL)window.libURL = config.baseURL;
|
||||||
if(!window.libURL)window.libURL = originalURL;
|
if(!window.libURL)window.libURL = originalURL;
|
||||||
%>
|
%>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title><%-pageTitle%></title>
|
<title><%-pageTitle%></title>
|
||||||
|
<div><%-window.libURL%></div>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no' name='viewport' />
|
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no' name='viewport' />
|
||||||
<meta name="description" content="Shinobi">
|
<meta name="description" content="Shinobi">
|
||||||
|
|
@ -62,7 +64,7 @@
|
||||||
if(!details.sub){ %>
|
if(!details.sub){ %>
|
||||||
<script>
|
<script>
|
||||||
window.getAdminApiPrefix = function(piece){
|
window.getAdminApiPrefix = function(piece){
|
||||||
return location.search === '?p2p=1' ? location.pathname + '/' : "<%=originalURL%><%=config.webPaths.adminApiPrefix%><%- $user.auth_token %>/"
|
return location.search === '?p2p=1' ? (location.pathname.endsWith('/') ? location.pathname : location.pathname) : "<%=originalURL%><%=config.webPaths.adminApiPrefix%><%- $user.auth_token %>/"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<% }
|
<% }
|
||||||
|
|
|
||||||
|
|
@ -1,353 +0,0 @@
|
||||||
<div class="modal fade" id="help_window" tabindex="-1" role="dialog" aria-labelledby="help_windowLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
<h4 class="modal-title" id="help_windowLabel"><i class="fa fa-question-circle"></i> <span><%-lang.Help%></span></h4>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="row">
|
|
||||||
<% [
|
|
||||||
{
|
|
||||||
bigIcon: "smile-o",
|
|
||||||
title: "It's a proven fact",
|
|
||||||
info: `Generosity makes you a happier person, please consider supporting the development.`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
icon: 'share-square-o',
|
|
||||||
color: 'default',
|
|
||||||
text: 'ShinobiShop Subscriptions',
|
|
||||||
href: 'https://licenses.shinobi.video/subscribe',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'paypal',
|
|
||||||
color: 'default',
|
|
||||||
text: 'Donate by PayPal',
|
|
||||||
href: 'https://www.paypal.me/ShinobiCCTV',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'bank',
|
|
||||||
color: 'default',
|
|
||||||
text: 'University of Zurich (UZH)',
|
|
||||||
href: 'https://www.media.uzh.ch/en/Press-Releases/2017/Generosity.html',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "New to Shinobi?",
|
|
||||||
info: `Try reading over some of these links to get yourself started.`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
icon: 'newspaper-o',
|
|
||||||
color: 'default',
|
|
||||||
text: 'After Installation Guides',
|
|
||||||
href: 'https://shinobi.video/docs/configure',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'plus',
|
|
||||||
color: 'default',
|
|
||||||
text: 'Adding an H.264 Camera',
|
|
||||||
href: 'https://shinobi.video/docs/configure#content-adding-an-h264h265-camera',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'plus',
|
|
||||||
color: 'default',
|
|
||||||
text: 'Adding an MJPEG Camera',
|
|
||||||
href: 'https://shinobi.video/articles/2018-09-19-how-to-add-an-mjpeg-camera',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'gears',
|
|
||||||
color: 'default',
|
|
||||||
text: 'RTSP Camera Optimization',
|
|
||||||
href: 'https://shinobi.video/articles/2017-07-29-how-i-optimized-my-rtsp-camera',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'comments-o',
|
|
||||||
color: 'info',
|
|
||||||
text: 'Community Chat',
|
|
||||||
href: 'https://discord.gg/ehRd8Zz',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'reddit',
|
|
||||||
color: 'info',
|
|
||||||
text: 'Forum on Reddit',
|
|
||||||
href: 'https://www.reddit.com/r/ShinobiCCTV',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'file-o',
|
|
||||||
color: 'primary',
|
|
||||||
text: 'Documentation',
|
|
||||||
href: 'http://shinobi.video/docs',
|
|
||||||
class: ''
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "ShinobiHub",
|
|
||||||
info: `Explore and Contribute to the community of Shinobi all in one place.`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
icon: 'newspaper-o',
|
|
||||||
color: 'primary',
|
|
||||||
text: 'Read Articles',
|
|
||||||
href: 'https://hub.shinobi.video/articles',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'search',
|
|
||||||
color: 'success',
|
|
||||||
text: 'Explore Configurations',
|
|
||||||
href: 'https://hub.shinobi.video/explore',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'lightbulb-o',
|
|
||||||
color: 'info',
|
|
||||||
text: 'View Suggestions',
|
|
||||||
href: 'https://hub.shinobi.video/suggestions',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Shinobi Mobile",
|
|
||||||
info: `Your subscription key can unlock features for <a href="https://cdn.shinobi.video/installers/ShinobiMobile/" target="_blank"><b>Shinobi Mobile</b></a> running on iOS and Android today!`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
icon: 'star',
|
|
||||||
color: 'success',
|
|
||||||
text: 'Join Public Beta',
|
|
||||||
href: 'https://cdn.shinobi.video/installers/ShinobiMobile/',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'comments-o',
|
|
||||||
color: 'primary',
|
|
||||||
text: '<b>#mobile-client</b> Chat',
|
|
||||||
href: 'https://discord.gg/ehRd8Zz',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'cube',
|
|
||||||
color: 'default',
|
|
||||||
text: lang[`Don't Show for 1 Week`],
|
|
||||||
class: 'hide_donate',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Shinobi Dashcam",
|
|
||||||
info: `<a href="https://cdn.shinobi.video/installers/Dashcam/" target="_blank"><b>Shinobi Dashcam</b></a> runs on iOS and Android, Subscribe to a Shinobi Mobile license to unlock extended features!`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
icon: 'star',
|
|
||||||
color: 'success',
|
|
||||||
text: 'Join Public Beta',
|
|
||||||
href: 'https://cdn.shinobi.video/installers/Dashcam/',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'comments-o',
|
|
||||||
color: 'primary',
|
|
||||||
text: '<b>#dashcam</b> Chat',
|
|
||||||
href: 'https://discord.gg/D3cHZ3u',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'cube',
|
|
||||||
color: 'default',
|
|
||||||
text: lang[`Don't Show for 1 Week`],
|
|
||||||
class: 'hide_donate',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Byaku",
|
|
||||||
info: `Byaku is a Neural Net Training tool that allows you to build Object Detection sets. <a href="https://cdn.shinobi.video/installers/Byaku/" target="_blank"><b>Byaku</b></a> runs on Linux. Subscribe to use features like automatically downloading thousands of images already pre-annotated and ready for you to train.`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
icon: 'star',
|
|
||||||
color: 'success',
|
|
||||||
text: 'Join Public Beta',
|
|
||||||
href: 'https://cdn.shinobi.video/installers/Byaku/',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'comments-o',
|
|
||||||
color: 'primary',
|
|
||||||
text: '<b>#byaku</b> Chat',
|
|
||||||
href: 'https://discord.gg/3XA9RNP',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'cube',
|
|
||||||
color: 'default',
|
|
||||||
text: lang[`Don't Show for 1 Week`],
|
|
||||||
class: 'hide_donate',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Support the Development",
|
|
||||||
info: `Subscribe to any of the following to boost development! Once subscribed put your Subscription ID in at the Super user panel, then restart Shinobi to Activate your installation, thanks! <i class="fa fa-smile-o"></i>`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
icon: 'share-square-o',
|
|
||||||
color: 'default',
|
|
||||||
text: 'Shinobi Mobile License ($5/m)',
|
|
||||||
href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G31AZ9mknNCa6z',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'share-square-o',
|
|
||||||
color: 'default',
|
|
||||||
text: 'Tiny Support Subscription ($10/m)',
|
|
||||||
href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G42jNgIqXaWmIC',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'share-square-o',
|
|
||||||
color: 'default',
|
|
||||||
text: 'Shinobi Pro License ($75/m)',
|
|
||||||
href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G3LGdNwA8lSmQy',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Donations, One-Time Boost",
|
|
||||||
info: `Sometimes a subscription isn't practical for people. In which case you may show support through a PayPal donation. And as a thank you for doing so your <b>PayPal Transaction ID</b> can be used as a <code>subscriptionId</code> in your Shinobi configuration file. <br><br>Each 5 USD/EUR or 7 CAD will provide one month of activated usage. <i>Meaning, a $20 USD donation today makes this popup go away (or activates the mobile app) for 4 months.</i>`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
icon: 'paypal',
|
|
||||||
color: 'default',
|
|
||||||
text: 'Donate by PayPal',
|
|
||||||
href: 'https://www.paypal.me/ShinobiCCTV',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
].forEach((promo) => { %>
|
|
||||||
<div class="col-md-<%- promo.width || '6' %>">
|
|
||||||
<div class="helpquote" style="margin-bottom:5px;font-size:10pt;padding: 10px 20px;border-left: 5px solid #eee;">
|
|
||||||
<%- promo.bigIcon ? `<div style="margin-bottom:15px;text-align:center"><i class="fa fa-${promo.bigIcon} fa-5x"></i></div>` : '' %>
|
|
||||||
<%- promo.title ? `<h4>${promo.title}</h4>` : '' %>
|
|
||||||
<p><%- promo.info %></p>
|
|
||||||
<% if(promo.buttons) { %>
|
|
||||||
<div style="margin-top:5px;">
|
|
||||||
<% promo.buttons.forEach((button) => { %>
|
|
||||||
<a style="margin-bottom:4px" <%- button.href ? `href="${button.href}"` : '' %> target="_blank" class="d-flex flex-row btn btn-sm btn-block btn-<%- button.color %> <%- button.class %>">
|
|
||||||
<div><i class="fa fa-<%- button.icon %>" aria-hidden="true"></i></div>
|
|
||||||
<div class="flex-grow-1 text-center"><%- button.text %></div>
|
|
||||||
</a>
|
|
||||||
<% }) %>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% }) %>
|
|
||||||
</div>
|
|
||||||
<blockquote class="blockquoteInHelp" style="margin-bottom:10px">
|
|
||||||
<p>If you already are supporting the development in a different way, please contact us and we can get this popup to go away for you <i class="fa fa-smile-o"></i> Cheers!</p>
|
|
||||||
</blockquote>
|
|
||||||
<div>
|
|
||||||
<!-- <iframe src="https://shinobi.video/ads" style="width:100%;height:250px;border:none;"></iframe> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-danger pull-left hide_donate"><i class="fa fa-cube"></i> <%-lang[`Don't Show for 1 Week`]%></button>
|
|
||||||
<a target="_blank" href="https://discord.gg/gCxefHA" class="btn btn-primary"><i class="fa fa-comments-o"></i> <%-lang['Chat on Discord']%></a>
|
|
||||||
<a target="_blank" href="http://shinobi.video/docs" class="btn btn-default"><i class="fa fa-file-o"></i> <%-lang.Documentation%></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<style>
|
|
||||||
.blockquoteInHelp:before,.blockquoteInHelp:after{
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
$(document).ready(function(){
|
|
||||||
var helpWindow = $('#help_window')
|
|
||||||
var openMessage = null
|
|
||||||
function lessThanOneWeekAgo(date){
|
|
||||||
const WEEK = 1000 * 60 * 60 * 24 * 7;
|
|
||||||
const aWeekAgo = Date.now() - WEEK;
|
|
||||||
|
|
||||||
return date < aWeekAgo;
|
|
||||||
}
|
|
||||||
function showHelpNotice(){
|
|
||||||
var buttonHtml = ``
|
|
||||||
$.each([
|
|
||||||
{
|
|
||||||
icon: 'share-square-o',
|
|
||||||
color: 'default',
|
|
||||||
text: 'ShinobiShop Subscriptions',
|
|
||||||
href: 'https://licenses.shinobi.video/subscribe',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'paypal',
|
|
||||||
color: 'success',
|
|
||||||
text: 'Donate by PayPal',
|
|
||||||
href: 'https://www.paypal.me/ShinobiCCTV',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'bank',
|
|
||||||
color: 'default',
|
|
||||||
text: 'University of Zurich (UZH)',
|
|
||||||
href: 'https://www.media.uzh.ch/en/Press-Releases/2017/Generosity.html',
|
|
||||||
class: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'cube',
|
|
||||||
color: 'danger',
|
|
||||||
text: lang[`Don't Show for 1 Week`],
|
|
||||||
href: '#',
|
|
||||||
class: 'hide_donate',
|
|
||||||
},
|
|
||||||
],function(n,button){
|
|
||||||
buttonHtml += `<a style="margin-bottom:4px" ${ button.href ? `href="${button.href}"` : '' } target="_blank" class="d-flex flex-row btn btn-sm btn-block btn-${ button.color } ${ button.class }">
|
|
||||||
<div><i class="fa fa-${ button.icon }" aria-hidden="true"></i></div>
|
|
||||||
<div class="text-center flex-grow-1">${ button.text }</div>
|
|
||||||
</a>`
|
|
||||||
})
|
|
||||||
openMessage = new PNotify({
|
|
||||||
title: `It's a proven fact`,
|
|
||||||
text: `<div class="mb-3">Generosity makes you a happier person, please consider supporting the development.</div>${buttonHtml}`,
|
|
||||||
hide: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function dontShowForOneWeek(){
|
|
||||||
if(openMessage){
|
|
||||||
openMessage.remove()
|
|
||||||
}
|
|
||||||
dashboardOptions('subscription_checked',new Date());
|
|
||||||
}
|
|
||||||
if(<%- !config.userHasSubscribed %> && !dashboardOptions().subscription_checked || lessThanOneWeekAgo(new Date(dashboardOptions().subscription_checked))){
|
|
||||||
setTimeout(function(){
|
|
||||||
showHelpNotice()
|
|
||||||
},1000 * 60 * 0.2)
|
|
||||||
}
|
|
||||||
$('body').on('click','.hide_donate',function(e){
|
|
||||||
e.preventDefault()
|
|
||||||
dontShowForOneWeek()
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
console.log('Please support the Shinobi developement.')
|
|
||||||
console.log('https://licenses.shinobi.video/subscribe')
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
@ -21,12 +21,13 @@
|
||||||
'home/cameraProbe',
|
'home/cameraProbe',
|
||||||
'home/powerVideo',
|
'home/powerVideo',
|
||||||
'home/onvifScanner',
|
'home/onvifScanner',
|
||||||
|
'home/onvifDeviceManager',
|
||||||
'home/configFinder',
|
'home/configFinder',
|
||||||
'home/logViewer',
|
'home/logViewer',
|
||||||
'home/calendar',
|
'home/calendar',
|
||||||
'home/eventListWithPics',
|
'home/eventListWithPics',
|
||||||
'confirm',
|
'confirm',
|
||||||
'help',
|
'home/help',
|
||||||
]).forEach(function(block){ %>
|
]).forEach(function(block){ %>
|
||||||
<%- include(__dirname + '/web/pages/blocks/' +block + '.ejs') %>
|
<%- include(__dirname + '/web/pages/blocks/' +block + '.ejs') %>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,266 @@
|
||||||
|
<main class="container page-tab pt-3" id="tab-helpWindow">
|
||||||
|
<div style="" class="card form-group-group p-3 bg-dark mb-3 shadow green">
|
||||||
|
<h4 class="form-section-header cursor-pointer mb-3 pb-3 text-white border-bottom-dotted border-color-green "><%- config.poweredByShinobi %></h4>
|
||||||
|
<div class="card-body text-white">
|
||||||
|
<div class="row">
|
||||||
|
<% [
|
||||||
|
{
|
||||||
|
title: "New to Shinobi?",
|
||||||
|
info: `Try reading over some of these links to get yourself started.`,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
icon: 'newspaper-o',
|
||||||
|
color: 'default',
|
||||||
|
text: 'After Installation Guides',
|
||||||
|
href: 'https://shinobi.video/docs/configure',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'plus',
|
||||||
|
color: 'default',
|
||||||
|
text: 'Adding an H.264 Camera',
|
||||||
|
href: 'https://shinobi.video/docs/configure#content-adding-an-h264h265-camera',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'plus',
|
||||||
|
color: 'default',
|
||||||
|
text: 'Adding an MJPEG Camera',
|
||||||
|
href: 'https://shinobi.video/articles/2018-09-19-how-to-add-an-mjpeg-camera',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'gears',
|
||||||
|
color: 'default',
|
||||||
|
text: 'RTSP Camera Optimization',
|
||||||
|
href: 'https://shinobi.video/articles/2017-07-29-how-i-optimized-my-rtsp-camera',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'comments-o',
|
||||||
|
color: 'info',
|
||||||
|
text: 'Community Chat',
|
||||||
|
href: 'https://discord.gg/ehRd8Zz',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'reddit',
|
||||||
|
color: 'info',
|
||||||
|
text: 'Forum on Reddit',
|
||||||
|
href: 'https://www.reddit.com/r/ShinobiCCTV',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'file-o',
|
||||||
|
color: 'primary',
|
||||||
|
text: 'Documentation',
|
||||||
|
href: 'http://shinobi.video/docs',
|
||||||
|
class: ''
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
bigIcon: "smile-o",
|
||||||
|
title: "It's a proven fact",
|
||||||
|
info: `Generosity makes you a happier person, please consider supporting the development.`,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
icon: 'share-square-o',
|
||||||
|
color: 'default',
|
||||||
|
text: 'ShinobiShop Subscriptions',
|
||||||
|
href: 'https://licenses.shinobi.video/subscribe',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'paypal',
|
||||||
|
color: 'default',
|
||||||
|
text: 'Donate by PayPal',
|
||||||
|
href: 'https://www.paypal.me/ShinobiCCTV',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'bank',
|
||||||
|
color: 'default',
|
||||||
|
text: 'University of Zurich (UZH)',
|
||||||
|
href: 'https://www.media.uzh.ch/en/Press-Releases/2017/Generosity.html',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "ShinobiHub",
|
||||||
|
// info: `Explore and Contribute to the community of Shinobi all in one place.`,
|
||||||
|
// buttons: [
|
||||||
|
// {
|
||||||
|
// icon: 'newspaper-o',
|
||||||
|
// color: 'primary',
|
||||||
|
// text: 'Read Articles',
|
||||||
|
// href: 'https://hub.shinobi.video/articles',
|
||||||
|
// class: ''
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// icon: 'search',
|
||||||
|
// color: 'success',
|
||||||
|
// text: 'Explore Configurations',
|
||||||
|
// href: 'https://hub.shinobi.video/explore',
|
||||||
|
// class: ''
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// icon: 'lightbulb-o',
|
||||||
|
// color: 'info',
|
||||||
|
// text: 'View Suggestions',
|
||||||
|
// href: 'https://hub.shinobi.video/suggestions',
|
||||||
|
// class: ''
|
||||||
|
// },
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: "Shinobi Mobile",
|
||||||
|
info: `Your subscription key can unlock features for <a href="https://cdn.shinobi.video/installers/ShinobiMobile/" target="_blank"><b>Shinobi Mobile</b></a> running on iOS and Android today!`,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
icon: 'star',
|
||||||
|
color: 'success',
|
||||||
|
text: 'Join Public Beta',
|
||||||
|
href: 'https://shinobi.video/mobile',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'comments-o',
|
||||||
|
color: 'primary',
|
||||||
|
text: '<b>#mobile-client</b> Chat',
|
||||||
|
href: 'https://discord.gg/ehRd8Zz',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// icon: 'cube',
|
||||||
|
// color: 'default',
|
||||||
|
// text: lang[`Don't Show for 1 Week`],
|
||||||
|
// class: 'hide_donate',
|
||||||
|
// },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "Shinobi Dashcam",
|
||||||
|
// info: `<a href="https://cdn.shinobi.video/installers/Dashcam/" target="_blank"><b>Shinobi Dashcam</b></a> runs on iOS and Android, Subscribe to a Shinobi Mobile license to unlock extended features!`,
|
||||||
|
// buttons: [
|
||||||
|
// {
|
||||||
|
// icon: 'star',
|
||||||
|
// color: 'success',
|
||||||
|
// text: 'Join Public Beta',
|
||||||
|
// href: 'https://cdn.shinobi.video/installers/Dashcam/',
|
||||||
|
// class: ''
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// icon: 'comments-o',
|
||||||
|
// color: 'primary',
|
||||||
|
// text: '<b>#dashcam</b> Chat',
|
||||||
|
// href: 'https://discord.gg/D3cHZ3u',
|
||||||
|
// class: ''
|
||||||
|
// },
|
||||||
|
// // {
|
||||||
|
// // icon: 'cube',
|
||||||
|
// // color: 'default',
|
||||||
|
// // text: lang[`Don't Show for 1 Week`],
|
||||||
|
// // class: 'hide_donate',
|
||||||
|
// // },
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: "Byaku",
|
||||||
|
// info: `Byaku is a Neural Net Training tool that allows you to build Object Detection sets. <a href="https://cdn.shinobi.video/installers/Byaku/" target="_blank"><b>Byaku</b></a> runs on Linux. Subscribe to use features like automatically downloading thousands of images already pre-annotated and ready for you to train.`,
|
||||||
|
// buttons: [
|
||||||
|
// {
|
||||||
|
// icon: 'star',
|
||||||
|
// color: 'success',
|
||||||
|
// text: 'Join Public Beta',
|
||||||
|
// href: 'https://cdn.shinobi.video/installers/Byaku/',
|
||||||
|
// class: ''
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// icon: 'comments-o',
|
||||||
|
// color: 'primary',
|
||||||
|
// text: '<b>#byaku</b> Chat',
|
||||||
|
// href: 'https://discord.gg/3XA9RNP',
|
||||||
|
// class: ''
|
||||||
|
// },
|
||||||
|
// // {
|
||||||
|
// // icon: 'cube',
|
||||||
|
// // color: 'default',
|
||||||
|
// // text: lang[`Don't Show for 1 Week`],
|
||||||
|
// // class: 'hide_donate',
|
||||||
|
// // },
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
title: "Support the Development",
|
||||||
|
info: `Subscribe to any of the following to boost development! Once subscribed put your Subscription ID in at the Super user panel, then restart Shinobi to Activate your installation, thanks! <i class="fa fa-smile-o"></i>`,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
icon: 'share-square-o',
|
||||||
|
color: 'default',
|
||||||
|
text: 'Shinobi Mobile License ($5/m)',
|
||||||
|
href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G31AZ9mknNCa6z',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'share-square-o',
|
||||||
|
color: 'default',
|
||||||
|
text: 'Tiny Support Subscription ($10/m)',
|
||||||
|
href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G42jNgIqXaWmIC',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'share-square-o',
|
||||||
|
color: 'default',
|
||||||
|
text: 'Shinobi Pro License ($75/m)',
|
||||||
|
href: 'https://licenses.shinobi.video/subscribe?planSubscribe=plan_G3LGdNwA8lSmQy',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Donations, One-Time Boost",
|
||||||
|
info: `Sometimes a subscription isn't practical for people. In which case you may show support through a PayPal donation. And as a thank you for doing so your <b>PayPal Transaction ID</b> can be used as a <code>subscriptionId</code> in your Shinobi configuration file. <br><br>Each 5 USD/EUR or 7 CAD will provide one month of activated usage. <i>Meaning, a $20 USD donation today makes this popup go away (or activates the mobile app) for 4 months.</i>`,
|
||||||
|
width: 12,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
icon: 'paypal',
|
||||||
|
color: 'default',
|
||||||
|
text: 'Donate by PayPal',
|
||||||
|
href: 'https://www.paypal.me/ShinobiCCTV',
|
||||||
|
class: ''
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
].forEach((promo) => { %>
|
||||||
|
<div class="col-md-<%- promo.width || '6' %> mb-3">
|
||||||
|
<div class="helpquote" style="font-size:10pt;padding: 10px 20px;border-left: 5px solid #eee;">
|
||||||
|
<%- promo.bigIcon ? `<div style="margin-bottom:15px;text-align:center"><i class="fa fa-${promo.bigIcon} fa-5x"></i></div>` : '' %>
|
||||||
|
<%- promo.title ? `<h4>${promo.title}</h4>` : '' %>
|
||||||
|
<p><%- promo.info %></p>
|
||||||
|
<% if(promo.buttons) { %>
|
||||||
|
<div style="margin-top:5px;">
|
||||||
|
<% promo.buttons.forEach((button) => { %>
|
||||||
|
<a style="margin-bottom:4px" <%- button.href ? `href="${button.href}"` : '' %> target="_blank" class="d-flex flex-row btn btn-sm btn-block btn-<%- button.color %> <%- button.class %>">
|
||||||
|
<div><i class="fa fa-<%- button.icon %>" aria-hidden="true"></i></div>
|
||||||
|
<div class="flex-grow-1 text-center"><%- button.text %></div>
|
||||||
|
</a>
|
||||||
|
<% }) %>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% }) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.blockquoteInHelp:before,.blockquoteInHelp:after{
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>userHasSubscribed = <%- !!config.userHasSubscribed %>;</script>
|
||||||
|
<script src="<%-window.libURL%>assets/js/bs5.help.js"></script>
|
||||||
|
</main>
|
||||||
|
|
@ -2,18 +2,18 @@
|
||||||
const showMonitors = define.SideMenu.showMonitors; %>
|
const showMonitors = define.SideMenu.showMonitors; %>
|
||||||
<nav id="<%- menuInfo.id || 'sidebarMenu' %>" class="<%- menuInfo.class || 'col-md-3 col-lg-2 d-md-block sidebar collapse' %>">
|
<nav id="<%- menuInfo.id || 'sidebarMenu' %>" class="<%- menuInfo.class || 'col-md-3 col-lg-2 d-md-block sidebar collapse' %>">
|
||||||
<div id="menu-side" class="position-sticky">
|
<div id="menu-side" class="position-sticky">
|
||||||
<div class="align-items-center d-flex flex-row p-3 my-3 m-3 btn-default rounded shadow-sm cursor-pointer" style="overflow: hidden;">
|
<div class="align-items-center d-flex flex-row my-3 m-3 btn-default rounded shadow-sm cursor-pointer" style="overflow: hidden;">
|
||||||
<div class="align-items-center mr-3">
|
<div class="align-items-center mr-3 py-3 pl-3">
|
||||||
<div class="lh-1" <%- showMonitors ? `data-bs-toggle="collapse" data-bs-target=".home-collapse"` : '' %>>
|
<div class="lh-1 <%- showMonitors ? `toggle-menu-collapse` : '' %>">
|
||||||
<img src="<%- window.libURL + config.logoLocation196x196 %>" width="42" style="<%- config.logoLocation76x76Style %>">
|
<img src="<%- window.libURL + config.logoLocation196x196 %>" width="42" style="<%- config.logoLocation76x76Style %>">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow-1 align-items-center mr-3" <%- showMonitors ? `data-bs-toggle="collapse" data-bs-target=".home-collapse"` : '' %>>
|
<div class="flex-grow-1 align-items-center mr-3 py-3 <%- !showMonitors ? `pr-3` : '' %> <%- showMonitors ? `toggle-menu-collapse` : '' %>">
|
||||||
<h1 class="h6 mb-0 lh-1 text-ellipsis" style="overflow: hidden;"><%- $user.mail %></h1>
|
<h1 class="h6 mb-0 lh-1 text-ellipsis" style="overflow: hidden;"><%- $user.mail %></h1>
|
||||||
<small><%- lang.Monitors %> : <span class="cameraCount"></span></small>
|
<small><%- lang.Monitors %> : <span class="cameraCount"></span></small>
|
||||||
</div>
|
</div>
|
||||||
<% if(showMonitors){ %>
|
<% if(showMonitors){ %>
|
||||||
<div class="align-items-center mr-3 btn-default rounded shadow-sm cursor-pointer">
|
<div class="align-items-center mr-3 py-3 pr-3 btn-default rounded shadow-sm cursor-pointer">
|
||||||
<div class="lh-1">
|
<div class="lh-1">
|
||||||
<a class_toggle="compressed-monitor-icons" data-target="#monitorSideList" icon-toggle="fa-th fa-bars" icon-child="i"><i class="fa fa-th"></i></a>
|
<a class_toggle="compressed-monitor-icons" data-target="#monitorSideList" icon-toggle="fa-th fa-bars" icon-child="i"><i class="fa fa-th"></i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
include fieldBuilders.ejs
|
include fieldBuilders.ejs
|
||||||
%>
|
%>
|
||||||
<% drawBlock(define.SideMenu.blocks.SideMenuBeforeList) %>
|
<% drawBlock(define.SideMenu.blocks.SideMenuBeforeList) %>
|
||||||
<div class="collapse show home-collapse">
|
<div id="side-menu-collapse-point" class="collapse show home-collapse">
|
||||||
<ul id="pageTabLinks" class="nav flex-column nav-pills">
|
<ul id="pageTabLinks" class="nav flex-column nav-pills">
|
||||||
<div class="pb-2 px-3" id="home-collapse">
|
<div class="pb-2 px-3" id="home-collapse">
|
||||||
<% menuInfo.links.forEach((item) => {
|
<% menuInfo.links.forEach((item) => {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<main class="container page-tab pt-3" id="tab-onvifDeviceManager">
|
||||||
|
<form class="dark row">
|
||||||
|
<%
|
||||||
|
var drawBlock
|
||||||
|
var buildOptions
|
||||||
|
%>
|
||||||
|
<%
|
||||||
|
include fieldBuilders.ejs
|
||||||
|
%>
|
||||||
|
<% Object.keys(define['ONVIF Device Manager'].blocks).forEach(function(blockKey){
|
||||||
|
var accountSettings = define['ONVIF Device Manager'].blocks[blockKey]
|
||||||
|
drawBlock(accountSettings)
|
||||||
|
}) %>
|
||||||
|
<div class="col-md-12">
|
||||||
|
<%
|
||||||
|
include stickySubmitBar.ejs
|
||||||
|
%>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
<script src="<%-window.libURL%>assets/js/bs5.onvifDeviceManager.js"></script>
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
<div class="my-3 p-3 <%- define.Theme.isDark ? 'bg-dark text-white' : 'bg-light' %> rounded shadow-sm" id="recentVideos">
|
<div class="my-3 p-3 <%- define.Theme.isDark ? 'bg-dark text-white' : 'bg-light' %> rounded shadow-sm" id="recentVideos">
|
||||||
<h4 class="mb-3 pb-3 <%- define.Theme.isDark ? 'text-white' : '' %> border-bottom-dotted border-color-blue row">
|
<h4 class="mb-3 pb-3 <%- define.Theme.isDark ? 'text-white' : '' %> border-bottom-dotted border-color-blue row">
|
||||||
<div class="col-md-8">
|
<div class="col-md-7">
|
||||||
<%- lang['Recent Videos'] %>
|
<%- lang['Recent Videos'] %>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-1 text-right">
|
||||||
|
<a class="btn btn-primary btn-sm recent-videos-refresh"> <i class="fa fa-refresh"> </i></a>
|
||||||
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<select class="form-control form-control-sm <%- define.Theme.isDark ? 'btn-dark' : '' %> text-start monitors_list" type="text" id="recentVideosMonitorId">
|
<select class="form-control form-control-sm <%- define.Theme.isDark ? 'btn-dark' : '' %> text-start monitors_list" type="text" id="recentVideosMonitorId">
|
||||||
<option value=""><%- lang['All Monitors'] %></option>
|
<option value=""><%- lang['All Monitors'] %></option>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
<head>
|
<head>
|
||||||
<!-- Powered by Shinobi, http://shinobi.video -->
|
<!-- Powered by Shinobi, http://shinobi.video -->
|
||||||
<% include blocks/header-title.ejs %>
|
<% include blocks/header-title.ejs %>
|
||||||
<% if(!window.libURL)window.libURL = originalURL + global.s.checkCorrectPathEnding(config.webPaths.home) %>
|
<%
|
||||||
|
if(config.baseURL)window.libURL = config.baseURL;
|
||||||
|
if(!window.libURL)window.libURL = originalURL;
|
||||||
|
%>
|
||||||
<% include blocks/header-meta.ejs %>
|
<% include blocks/header-meta.ejs %>
|
||||||
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
|
<meta http-equiv="content-type" content="text/html;charset=UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
a {cursor:pointer}
|
a {cursor:pointer}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
var superApiPrefix = location.search === '?p2p=1' ? location.pathname + '/' : "<%=originalURL%><%=config.webPaths.superApiPrefix%>"
|
var superApiPrefix = location.search === '?p2p=1' ? (location.pathname.endsWith('/') ? location.pathname : location.pathname) : "<%=originalURL%><%=config.webPaths.superApiPrefix%>"
|
||||||
</script>
|
</script>
|
||||||
<% customAutoLoad.superLibsCss.forEach(function(lib){ %>
|
<% customAutoLoad.superLibsCss.forEach(function(lib){ %>
|
||||||
<link rel="stylesheet" href="<%-window.libURL%>libs/css/<%-lib%>">
|
<link rel="stylesheet" href="<%-window.libURL%>libs/css/<%-lib%>">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue