Merge branch 'dev' into 'cron-as-worker-process'
# Conflicts: # libs/socketio.jscron-as-worker-process
commit
5d9db1124b
166
COPYING.md
166
COPYING.md
|
@ -3,95 +3,119 @@ SHINOBI OPEN SOURCE SOFTWARE LICENSE AGREEMENT
|
|||
Version 1, 04 June 2018
|
||||
|
||||
Copyright (C) 2018 [Shinobi Systems](https://shinobi.systems)
|
||||
|
||||
|
||||
*We'll try to keep it simple. Thanks for using Shinobi Software!*
|
||||
|
||||
Defintions.
|
||||
|
||||
Definitions.
|
||||
-----------
|
||||
In this license, which also serves as a general End User License Agreement [EULA], the following
|
||||
In this license, which also serves as a general End User License Agreement [EULA], the following
|
||||
terms shall be interpreted by these definitions:
|
||||
* "EULA" shall mean this End User Licence Agreement
|
||||
* "Licensor" shall mean SHINOBI SYSTEMS
|
||||
* "Licensee" shall mean YOU, or the organisation (if any) on whose behalf YOU are taking the EULA.
|
||||
|
||||
"SOFTWARE PRODUCTS" or "SOFTWARE" or "PRODUCTS" shall mean the Software Product this License is
|
||||
included with and any additional modules or add-ons delivered by Shinobi Systems. The term
|
||||
"SOFTWARE PRODUCTS" or "SOFTWARE" or "PRODUCTS" shall mean the Software Product this License is
|
||||
included with and any additional modules or add-ons delivered by Shinobi Systems. The term
|
||||
"SOFTWARE" includes, to the extent provided by SHINOBI SYSTEMS:
|
||||
1) any revisions, updates and/or upgrades thereto;
|
||||
2) any data, image or executable files, databases, data engines, computer software, or similar
|
||||
2) any data, image or executable files, databases, data engines, computer software, or similar
|
||||
items customarily used or distributed with computer software products;
|
||||
3) anything in any form whatsoever intended to be used with or in conjunction with the SOFTWARE;
|
||||
4) any associated media, documentation (including physical, electronic and on-line) and printed
|
||||
4) any associated media, documentation (including physical, electronic and on-line) and printed
|
||||
materials (the "Documentation").
|
||||
|
||||
Purpose of the Agreement.
|
||||
-------------------------
|
||||
**The Short**: Protect the rights of this Software Product and the LICENSOR.
|
||||
|
||||
**The Long**: The LICENSOR grants the LICENSEE a non-exclusive, non-transferable and perpetual
|
||||
licence to use the SOFTWARE PRODUCTS listed therein and under the terms thereof. By accepting
|
||||
the terms and conditions established in this agreement, the LICENSEE does not acquire any
|
||||
ownership of copyright or other intellectual property rights in any part of the SOFTWARE
|
||||
PRODUCTS. The LICENSEE is only entitled to use the SOFTWARE PRODUCTS in accordance with the
|
||||
terms and conditions set forth by Shinobi Systems. By using the SOFTWARE PRODUCTS, the
|
||||
**The Long**: The LICENSOR grants the LICENSEE a non-exclusive, non-transferable and perpetual
|
||||
licence to use the SOFTWARE PRODUCTS listed therein and under the terms thereof. By accepting
|
||||
the terms and conditions established in this agreement, the LICENSEE does not acquire any
|
||||
ownership of copyright or other intellectual property rights in any part of the SOFTWARE
|
||||
PRODUCTS. The LICENSEE is only entitled to use the SOFTWARE PRODUCTS in accordance with the
|
||||
terms and conditions set forth by Shinobi Systems. By using the SOFTWARE PRODUCTS, the
|
||||
LICENSEE agrees to accept the terms and conditions presented.
|
||||
|
||||
LICENSEE must purchase the applicable subscription in any other use case unless otherwise
|
||||
granted. If the use case does not have a subscription applicable please contact a
|
||||
LICENSEE must purchase the applicable subscription in any other use case unless otherwise
|
||||
granted. If the use case does not have a subscription applicable please contact a
|
||||
representative at support@shinobi.systems.
|
||||
|
||||
#### Commercial Uses
|
||||
- Selling usage of the software
|
||||
- Selling hardware with the software on it
|
||||
- Using the software in locations that engage in the buying and selling of goods and services
|
||||
|
||||
As of 2022-07-12 the noted situations below are seen the same as "Commercial Use".
|
||||
|
||||
- 25 Active Monitor Rule : Having at least 25 Active Monitors and not a Primary School or Secondary School. Does not apply to Personal Use.
|
||||
- 150 Active Monitor Rule : If you have more than 150 Active Monitors please contact support for an Enterprise License. The retail Shinobi Pro license will not be applicable for these installations.
|
||||
- Used on a Device that was part of a commercial transaction. This can be, but not limited to, being sold or provided additionally to a sale.
|
||||
|
||||
#### Conditions for Free (Unpaid) use.
|
||||
If you fall under any conditions for "Commercial Use" none of the following can apply unless otherwise noted.
|
||||
|
||||
- Personal use
|
||||
- Use in a non commercial area
|
||||
- Used for non commercial purposes
|
||||
- When used for research or educational purposes
|
||||
- Testing Purposes
|
||||
- Usage by Educational institutions
|
||||
- Use for Emergency Services and facilties associated like Search and Rescue Services or
|
||||
Ambulance Services
|
||||
- Use in Health Care facility like a hospital or walk-in clinic
|
||||
- Usage by Primary Education or Secondary Education Schools
|
||||
- Use for Emergency Services and facilities associated like Search and Rescue Services or
|
||||
Ambulance Services with less than 50 cameras
|
||||
- Use in Health Care facility like a hospital or walk-in clinic with less than 100 cameras
|
||||
- Schools and Organizations that have been given exemption
|
||||
|
||||
As of 2022-07-12 If you are an organization that falls under one or more of these conditions for free use then you must display that you are using Shinobi Systems software as "shinobi.video". This can be physically on premise or on your organization's public web page.
|
||||
|
||||
Falling under the conditions for Free use does not guarantee you a License Key. It simply means you are allowed to use it without paying or trading for that use.
|
||||
|
||||
#### Unsure if your use is commercial or non-commercial
|
||||
|
||||
Please contact us through the Live Chat of our website and we can negotiate or make exceptions based on the circumstances of your use case.
|
||||
|
||||
#### Registration
|
||||
|
||||
Schools and Resellers must register with Shinobi Systems at https://licenses.shinobi.video.
|
||||
In your account please fill out the "Apply for a School License" form or "Apply to be a Reseller" form.
|
||||
|
||||
#### Support Services.
|
||||
The Maintenance and Support Service shall be contracted and provided as per selected plan
|
||||
The Maintenance and Support Service shall be contracted and provided as per selected plan
|
||||
agreement, taxes will be included in all prices for Support Services.
|
||||
|
||||
Support Services will only provide support services as per the selected agreement.
|
||||
|
||||
This is not the entire agreement on support services. You must also review all agreements
|
||||
This is not the entire agreement on support services. You must also review all agreements
|
||||
provided with subscription plans provided.
|
||||
|
||||
#### Software Product Ownership.
|
||||
This software is property of Shinobi Systems. LICENSEE must keep all copyright notices
|
||||
This software is property of Shinobi Systems. LICENSEE must keep all copyright notices
|
||||
unchanged.
|
||||
|
||||
#### Modification of this Software Product.
|
||||
LICENSEE may modify code for personal use but must provide these changes upon request from
|
||||
Shinobi Systems or an authorized Shinobi representative. LICENSEE may not alter or change
|
||||
copyright notices. All code changes by LICENSEE shall fall under the copyright of Shinobi
|
||||
LICENSEE may modify code for but must provide these changes upon request from
|
||||
Shinobi Systems or an authorized Shinobi representative. LICENSEE may not alter or change
|
||||
copyright notices. All code changes by LICENSEE shall fall under the copyright of Shinobi
|
||||
Systems in the case code modified by LICENSEE is integrated into the official Shinobi code base.
|
||||
|
||||
#### Software Product Rebranding or "White-Labelling".
|
||||
LICENSEE can remove the Shinobi branding from the front end but all copyright notices must
|
||||
LICENSEE can remove the Shinobi branding from the front end but all copyright notices must
|
||||
remain unchanged.
|
||||
|
||||
#### Software Product Contributions.
|
||||
All contributed code becomes the property of Shinobi Systems. All contributors give permission
|
||||
All contributed code becomes the property of Shinobi Systems. All contributors give permission
|
||||
to Shinobi and Shinobi developers to use the code however it is seen fit.
|
||||
|
||||
#### Disclaimer of Warranty.
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
|
||||
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK
|
||||
AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM
|
||||
"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK
|
||||
AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE,
|
||||
YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
#### Changes to the Agreement.
|
||||
Shinobi Systems reserves the right to change the license and set of terms at any time.
|
||||
Continued use is agreement to those possible changes. Changes to this license will be provided
|
||||
Shinobi Systems reserves the right to change the license and set of terms at any time.
|
||||
Continued use is agreement to those possible changes. Changes to this license will be provided
|
||||
in the commit history of the repository it is located in.
|
||||
|
||||
#### Legal Proceedings.
|
||||
|
@ -100,31 +124,18 @@ All lawsuits must be filed at the Vancouver Court House.
|
|||
Courthouse Vancouver Robson Square
|
||||
800 Hornby St, Vancouver, BC V6Z 2C5
|
||||
|
||||
#### List of Included Software
|
||||
#### List of Included Software
|
||||
|
||||
This list is completed to best of our knowledge.
|
||||
This list is completed to the best of our knowledge.
|
||||
|
||||
Node.js - https://nodejs.org/en/
|
||||
MariaDB - https://mariadb.org/
|
||||
FFmpeg - https://www.ffmpeg.org/
|
||||
request - https://www.npmjs.com/package/request
|
||||
Express (npm) - https://expressjs.com/ https://www.npmjs.com/package/express
|
||||
EJS (npm) - http://ejs.co/ https://www.npmjs.com/package/ejs
|
||||
pam-diff (npm) (Motion Detector) - https://github.com/kevinGodell/pam-diff
|
||||
pipe2pam (npm) (for pam-diff) - https://github.com/kevinGodell/pipe2pam
|
||||
mp4frag (npm) (Poseidon's main engine) - https://github.com/kevinGodell/mp4frag
|
||||
pam-diff (Motion Detector) - https://github.com/kevinGodell/pam-diff
|
||||
pipe2pam (for pam-diff) - https://github.com/kevinGodell/pipe2pam
|
||||
mp4frag (Poseidon's main engine) - https://github.com/kevinGodell/mp4frag
|
||||
mse-live-player (for mp4frag) - https://github.com/kevinGodell/mse-live-player
|
||||
pipe2jpeg (npm) - https://github.com/kevinGodell/pipe2jpeg
|
||||
webdav (npm) - https://www.npmjs.com/package/webdav
|
||||
jsonfile (npm) - https://www.npmjs.com/package/jsonfile
|
||||
connectionTester (npm) - https://www.npmjs.com/package/connectionTester
|
||||
node-onvif (npm) - https://www.npmjs.com/package/node-onvif
|
||||
knex (npm) - https://www.npmjs.com/package/knex
|
||||
nodemailer (npm) - https://www.npmjs.com/package/nodemailer
|
||||
mysql (npm) - https://www.npmjs.com/package/mysql
|
||||
sqlite3 (npm) - https://www.npmjs.com/package/sqlite3
|
||||
ldapauth-fork (npm) - https://www.npmjs.com/package/ldapauth-fork
|
||||
http-proxy (npm) - https://www.npmjs.com/package/http-proxy
|
||||
pipe2jpeg - https://github.com/kevinGodell/pipe2jpeg
|
||||
hls.js - https://github.com/video-dev/hls.js/
|
||||
flv.js - https://github.com/Bilibili/flv.js/
|
||||
Material Design Lite - https://getmdl.io/
|
||||
|
@ -142,3 +153,50 @@ Courthouse Vancouver Robson Square
|
|||
Moment.js - https://momentjs.com/
|
||||
Livestamp.js - https://mattbradley.github.io/livestampjs/
|
||||
Lodash - https://lodash.com/
|
||||
|
||||
**npmjs.com packages**
|
||||
|
||||
async - https://www.npmjs.com/package/async
|
||||
aws-sdk - https://www.npmjs.com/package/aws-sdk
|
||||
backblaze-b2 - https://www.npmjs.com/package/backblaze-b2
|
||||
body-parser - https://www.npmjs.com/package/body-parser
|
||||
bson - https://www.npmjs.com/package/bson
|
||||
connection-tester - https://www.npmjs.com/package/connection-tester
|
||||
cws - https://www.npmjs.com/package/cws
|
||||
digest-fetch - https://www.npmjs.com/package/digest-fetch
|
||||
discord.js - https://www.npmjs.com/package/discord.js
|
||||
ejs - https://www.npmjs.com/package/ejs
|
||||
express - https://www.npmjs.com/package/express
|
||||
express-fileupload - https://www.npmjs.com/package/express-fileupload
|
||||
fs-extra - https://www.npmjs.com/package/fs-extra
|
||||
ftp-srv - https://www.npmjs.com/package/ftp-srv
|
||||
googleapis - https://www.npmjs.com/package/googleapis
|
||||
http-proxy - https://www.npmjs.com/package/http-proxy
|
||||
jsonfile - https://www.npmjs.com/package/jsonfile
|
||||
knex - https://www.npmjs.com/package/knex
|
||||
ldapauth-fork - https://www.npmjs.com/package/ldapauth-fork
|
||||
moment - https://www.npmjs.com/package/moment
|
||||
mp4frag - https://www.npmjs.com/package/mp4frag
|
||||
mqtt - https://www.npmjs.com/package/mqtt
|
||||
mysql - https://www.npmjs.com/package/mysql
|
||||
node-abort-controller - https://www.npmjs.com/package/node-abort-controller
|
||||
node-fetch - https://www.npmjs.com/package/node-fetch
|
||||
node-onvif-events - https://www.npmjs.com/package/node-onvif-events
|
||||
node-ssh - https://www.npmjs.com/package/node-ssh
|
||||
node-telegram-bot-api - https://www.npmjs.com/package/node-telegram-bot-api
|
||||
nodemailer - https://www.npmjs.com/package/nodemailer
|
||||
pam-diff - https://www.npmjs.com/package/pam-diff
|
||||
path - https://www.npmjs.com/package/path
|
||||
pipe2pam - https://www.npmjs.com/package/pipe2pam
|
||||
pixel-change - https://www.npmjs.com/package/pixel-change
|
||||
pushover-notifications - https://www.npmjs.com/package/pushover-notifications
|
||||
sat - https://www.npmjs.com/package/sat
|
||||
shinobi-onvif - https://www.npmjs.com/package/shinobi-onvif
|
||||
shinobi-sound-detection - https://www.npmjs.com/package/shinobi-sound-detection
|
||||
shinobi-zwave - https://www.npmjs.com/package/shinobi-zwave
|
||||
smtp-server - https://www.npmjs.com/package/smtp-server
|
||||
socket.io - https://www.npmjs.com/package/socket.io
|
||||
socket.io-client - https://www.npmjs.com/package/socket.io-client
|
||||
tree-kill - https://www.npmjs.com/package/tree-kill
|
||||
unzipper - https://www.npmjs.com/package/unzipper
|
||||
webdav-fs - https://www.npmjs.com/package/webdav-fs
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
|
||||
## Docker Ninja Way
|
||||
|
||||
> This method uses `docker-compose` and has the ability to quick install the TensorFlow Object Detection plugin.
|
||||
> This method uses `docker-compose` and has the ability to quick install the TensorFlow Object Detection plugin. This will build your container from the images hosted on Gitlab.
|
||||
|
||||
> **We no longer use Docker Hub** and will not in the foreseeable future.
|
||||
|
||||
Docker Image Used : `registry.gitlab.com/shinobi-systems/shinobi:dev`
|
||||
|
||||
```
|
||||
bash <(curl -s https://gitlab.com/Shinobi-Systems/Shinobi-Installer/raw/master/shinobi-docker.sh)
|
||||
|
@ -17,10 +21,11 @@ bash <(curl -s https://gitlab.com/Shinobi-Systems/Shinobi-Installer/raw/master/s
|
|||
> Please remember to check out the Environment Variables table further down this README.
|
||||
|
||||
```
|
||||
docker run -d --name='Shinobi' -p '8080:8080/tcp' -v "/dev/shm/Shinobi/streams":'/dev/shm/streams':'rw' -v "$HOME/Shinobi/config":'/config':'rw' -v "$HOME/Shinobi/customAutoLoad":'/home/Shinobi/libs/customAutoLoad':'rw' -v "$HOME/Shinobi/database":'/var/lib/mysql':'rw' -v "$HOME/Shinobi/videos":'/home/Shinobi/videos':'rw' -v "$HOME/Shinobi/plugins":'/home/Shinobi/plugins':'rw' -v '/etc/localtime':'/etc/localtime':'ro' shinobisystems/shinobi:dev
|
||||
docker run -d --name='Shinobi' -p '8080:8080/tcp' -v "/dev/shm/Shinobi/streams":'/dev/shm/streams':'rw' -v "$HOME/Shinobi/config":'/config':'rw' -v "$HOME/Shinobi/customAutoLoad":'/home/Shinobi/libs/customAutoLoad':'rw' -v "$HOME/Shinobi/database":'/var/lib/mysql':'rw' -v "$HOME/Shinobi/videos":'/home/Shinobi/videos':'rw' -v "$HOME/Shinobi/plugins":'/home/Shinobi/plugins':'rw' -v '/etc/localtime':'/etc/localtime':'ro' registry.gitlab.com/shinobi-systems/shinobi:dev
|
||||
```
|
||||
|
||||
#### Installing Object Detection (TensorFlow.js)
|
||||
**DEPRECATED, UPDATED IMAGE COMING SOON**
|
||||
|
||||
> This requires that you add the plugin key to the Shinobi container. This key is generated and displayed in the startup logs of the Object Detection docker container.
|
||||
|
||||
|
|
|
@ -4,10 +4,11 @@ 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/ubuntu1804/x86_64/cuda-repo-ubuntu1804_10.0.130-1_amd64.deb
|
||||
sudo dpkg -i --force-overwrite cuda-repo-ubuntu1804_10.0.130-1_amd64.deb
|
||||
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1804/x86_64/7fa2af80.pub
|
||||
|
||||
wget https://cdn.shinobi.video/installers/cuda-repo-ubuntu1804-10-0-local-10.0.130-410.48_1.0-1_amd64.deb -O cuda.deb
|
||||
dpkg -i cuda.deb
|
||||
sudo apt-key add /var/cuda-repo-10-0-local-10.0.130-410.48/7fa2af80.pub
|
||||
sudo apt-get update
|
||||
sudo apt install cuda-toolkit-10-0
|
||||
sudo apt-get update -y
|
||||
|
||||
sudo apt-get -o Dpkg::Options::="--force-overwrite" install cuda-toolkit-10-0 -y --no-install-recommends
|
||||
|
|
|
@ -34,7 +34,7 @@ echo "(y)es or (N)o"
|
|||
NODEJSINSTALL=0
|
||||
read -r nodejsinstall
|
||||
if [ "$nodejsinstall" = "y" ] || [ "$nodejsinstall" = "Y" ]; then
|
||||
sudo zypper install -y nodejs11
|
||||
sudo zypper install -y nodejs16
|
||||
NODEJSINSTALL=1
|
||||
fi
|
||||
echo "============="
|
||||
|
|
104
LICENSE.md
104
LICENSE.md
|
@ -42,17 +42,41 @@ representative at support@shinobi.systems.
|
|||
|
||||
#### Commercial Uses
|
||||
- Selling usage of the software
|
||||
- Selling hardware with the software on it
|
||||
- Using the software in locations that engage in the buying and selling of goods and services
|
||||
|
||||
As of 2022-07-12 the noted situations below are seen the same as "Commercial Use".
|
||||
|
||||
- 25 Active Monitor Rule : Having at least 25 Active Monitors and not a Primary School or Secondary School. Does not apply to Personal Use.
|
||||
- 150 Active Monitor Rule : If you have more than 150 Active Monitors please contact support for an Enterprise License. The retail Shinobi Pro license will not be applicable for these installations.
|
||||
- Used on a Device that was part of a commercial transaction. This can be, but not limited to, being sold or provided additionally to a sale.
|
||||
|
||||
#### Conditions for Free (Unpaid) use.
|
||||
If you fall under any conditions for "Commercial Use" none of the following can apply unless otherwise noted.
|
||||
|
||||
- Personal use
|
||||
- Use in a non commercial area
|
||||
- Used for non commercial purposes
|
||||
- When used for research or educational purposes
|
||||
- Testing Purposes
|
||||
- Usage by Educational institutions
|
||||
- Use for Emergency Services and facilties associated like Search and Rescue Services or
|
||||
Ambulance Services
|
||||
- Use in Health Care facility like a hospital or walk-in clinic
|
||||
- Usage by Primary Education or Secondary Education Schools
|
||||
- Use for Emergency Services and facilities associated like Search and Rescue Services or
|
||||
Ambulance Services with less than 50 cameras
|
||||
- Use in Health Care facility like a hospital or walk-in clinic with less than 100 cameras
|
||||
- Schools and Organizations that have been given exemption
|
||||
|
||||
As of 2022-07-12 If you are an organization that falls under one or more of these conditions for free use then you must display that you are using Shinobi Systems software as "shinobi.video". This can be physically on premise or on your organization's public web page.
|
||||
|
||||
Falling under the conditions for Free use does not guarantee you a License Key. It simply means you are allowed to use it without paying or trading for that use.
|
||||
|
||||
#### Unsure if your use is commercial or non-commercial
|
||||
|
||||
Please contact us through the Live Chat of our website and we can negotiate or make exceptions based on the circumstances of your use case.
|
||||
|
||||
#### Registration
|
||||
|
||||
Schools and Resellers must register with Shinobi Systems at https://licenses.shinobi.video.
|
||||
In your account please fill out the "Apply for a School License" form or "Apply to be a Reseller" form.
|
||||
|
||||
#### Support Services.
|
||||
The Maintenance and Support Service shall be contracted and provided as per selected plan
|
||||
|
@ -68,7 +92,7 @@ This software is property of Shinobi Systems. LICENSEE must keep all copyright n
|
|||
unchanged.
|
||||
|
||||
#### Modification of this Software Product.
|
||||
LICENSEE may modify code for but must provide these changes upon request from
|
||||
LICENSEE may modify code for but must provide these changes upon request from
|
||||
Shinobi Systems or an authorized Shinobi representative. LICENSEE may not alter or change
|
||||
copyright notices. All code changes by LICENSEE shall fall under the copyright of Shinobi
|
||||
Systems in the case code modified by LICENSEE is integrated into the official Shinobi code base.
|
||||
|
@ -102,29 +126,16 @@ Courthouse Vancouver Robson Square
|
|||
|
||||
#### List of Included Software
|
||||
|
||||
This list is completed to best of our knowledge.
|
||||
This list is completed to the best of our knowledge.
|
||||
|
||||
Node.js - https://nodejs.org/en/
|
||||
MariaDB - https://mariadb.org/
|
||||
FFmpeg - https://www.ffmpeg.org/
|
||||
request - https://www.npmjs.com/package/request
|
||||
Express (npm) - https://expressjs.com/ https://www.npmjs.com/package/express
|
||||
EJS (npm) - http://ejs.co/ https://www.npmjs.com/package/ejs
|
||||
pam-diff (npm) (Motion Detector) - https://github.com/kevinGodell/pam-diff
|
||||
pipe2pam (npm) (for pam-diff) - https://github.com/kevinGodell/pipe2pam
|
||||
mp4frag (npm) (Poseidon's main engine) - https://github.com/kevinGodell/mp4frag
|
||||
pam-diff (Motion Detector) - https://github.com/kevinGodell/pam-diff
|
||||
pipe2pam (for pam-diff) - https://github.com/kevinGodell/pipe2pam
|
||||
mp4frag (Poseidon's main engine) - https://github.com/kevinGodell/mp4frag
|
||||
mse-live-player (for mp4frag) - https://github.com/kevinGodell/mse-live-player
|
||||
pipe2jpeg (npm) - https://github.com/kevinGodell/pipe2jpeg
|
||||
webdav (npm) - https://www.npmjs.com/package/webdav
|
||||
jsonfile (npm) - https://www.npmjs.com/package/jsonfile
|
||||
connectionTester (npm) - https://www.npmjs.com/package/connectionTester
|
||||
node-onvif (npm) - https://www.npmjs.com/package/node-onvif
|
||||
knex (npm) - https://www.npmjs.com/package/knex
|
||||
nodemailer (npm) - https://www.npmjs.com/package/nodemailer
|
||||
mysql (npm) - https://www.npmjs.com/package/mysql
|
||||
sqlite3 (npm) - https://www.npmjs.com/package/sqlite3
|
||||
ldapauth-fork (npm) - https://www.npmjs.com/package/ldapauth-fork
|
||||
http-proxy (npm) - https://www.npmjs.com/package/http-proxy
|
||||
pipe2jpeg - https://github.com/kevinGodell/pipe2jpeg
|
||||
hls.js - https://github.com/video-dev/hls.js/
|
||||
flv.js - https://github.com/Bilibili/flv.js/
|
||||
Material Design Lite - https://getmdl.io/
|
||||
|
@ -142,3 +153,50 @@ Courthouse Vancouver Robson Square
|
|||
Moment.js - https://momentjs.com/
|
||||
Livestamp.js - https://mattbradley.github.io/livestampjs/
|
||||
Lodash - https://lodash.com/
|
||||
|
||||
**npmjs.com packages**
|
||||
|
||||
async - https://www.npmjs.com/package/async
|
||||
aws-sdk - https://www.npmjs.com/package/aws-sdk
|
||||
backblaze-b2 - https://www.npmjs.com/package/backblaze-b2
|
||||
body-parser - https://www.npmjs.com/package/body-parser
|
||||
bson - https://www.npmjs.com/package/bson
|
||||
connection-tester - https://www.npmjs.com/package/connection-tester
|
||||
cws - https://www.npmjs.com/package/cws
|
||||
digest-fetch - https://www.npmjs.com/package/digest-fetch
|
||||
discord.js - https://www.npmjs.com/package/discord.js
|
||||
ejs - https://www.npmjs.com/package/ejs
|
||||
express - https://www.npmjs.com/package/express
|
||||
express-fileupload - https://www.npmjs.com/package/express-fileupload
|
||||
fs-extra - https://www.npmjs.com/package/fs-extra
|
||||
ftp-srv - https://www.npmjs.com/package/ftp-srv
|
||||
googleapis - https://www.npmjs.com/package/googleapis
|
||||
http-proxy - https://www.npmjs.com/package/http-proxy
|
||||
jsonfile - https://www.npmjs.com/package/jsonfile
|
||||
knex - https://www.npmjs.com/package/knex
|
||||
ldapauth-fork - https://www.npmjs.com/package/ldapauth-fork
|
||||
moment - https://www.npmjs.com/package/moment
|
||||
mp4frag - https://www.npmjs.com/package/mp4frag
|
||||
mqtt - https://www.npmjs.com/package/mqtt
|
||||
mysql - https://www.npmjs.com/package/mysql
|
||||
node-abort-controller - https://www.npmjs.com/package/node-abort-controller
|
||||
node-fetch - https://www.npmjs.com/package/node-fetch
|
||||
node-onvif-events - https://www.npmjs.com/package/node-onvif-events
|
||||
node-ssh - https://www.npmjs.com/package/node-ssh
|
||||
node-telegram-bot-api - https://www.npmjs.com/package/node-telegram-bot-api
|
||||
nodemailer - https://www.npmjs.com/package/nodemailer
|
||||
pam-diff - https://www.npmjs.com/package/pam-diff
|
||||
path - https://www.npmjs.com/package/path
|
||||
pipe2pam - https://www.npmjs.com/package/pipe2pam
|
||||
pixel-change - https://www.npmjs.com/package/pixel-change
|
||||
pushover-notifications - https://www.npmjs.com/package/pushover-notifications
|
||||
sat - https://www.npmjs.com/package/sat
|
||||
shinobi-onvif - https://www.npmjs.com/package/shinobi-onvif
|
||||
shinobi-sound-detection - https://www.npmjs.com/package/shinobi-sound-detection
|
||||
shinobi-zwave - https://www.npmjs.com/package/shinobi-zwave
|
||||
smtp-server - https://www.npmjs.com/package/smtp-server
|
||||
socket.io - https://www.npmjs.com/package/socket.io
|
||||
socket.io-client - https://www.npmjs.com/package/socket.io-client
|
||||
tree-kill - https://www.npmjs.com/package/tree-kill
|
||||
unzipper - https://www.npmjs.com/package/unzipper
|
||||
webdav-fs - https://www.npmjs.com/package/webdav-fs
|
||||
|
|
2391
definitions/base.js
2391
definitions/base.js
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"Shinobi": "Shinobi",
|
||||
"superAdminTitle": "Shinobi : Super Admin",
|
||||
"superAdminTitle": "Shinobi : Super User",
|
||||
"failedLoginText1": "You have failed to login too many times. You must wait 15 minutes before trying again.",
|
||||
"failedLoginText2": "Please check your login credentials.",
|
||||
"Time Left": "Time Left",
|
||||
|
@ -16,6 +16,9 @@
|
|||
"accountActionFailed": "Account Action Failed",
|
||||
"deleteSubAccount": "Delete Sub-Account",
|
||||
"deleteSubAccountText": "Do you want to delete this Sub-Account? You cannot recover it.",
|
||||
"Accuracy Mode": "Accuracy Mode",
|
||||
"Tile Size": "Tile Size",
|
||||
"fieldTextTileSize": "When in Accuracy Mode this is the size of each tile in pixels squared. A lower number will have higher accuracy but more resource use.",
|
||||
"Turn Speed": "Turn Speed",
|
||||
"Session Key": "Session Key",
|
||||
"Active Monitors": "Active Monitors",
|
||||
|
@ -23,7 +26,9 @@
|
|||
"Use Raw Snapshot": "Use Raw Snapshot",
|
||||
"Account Edited": "Account Edited",
|
||||
"Failed to Edit Account": "Failed to Edit Account",
|
||||
"How to Connect": "How to Connect",
|
||||
"Login": "Login",
|
||||
"Room ID": "Room ID",
|
||||
"Substream": "Substream",
|
||||
"Use Substream": "Use Substream",
|
||||
"useSubStreamOnlyWhenWatching": "Only When Watching, Use Substream",
|
||||
|
@ -74,6 +79,7 @@
|
|||
"on": "on",
|
||||
"OAuth Credentials": "OAuth Credentials",
|
||||
"Token": "Token",
|
||||
"Admin Account Settings": "Admin Account Settings",
|
||||
"OAuth Code": "OAuth Code",
|
||||
"Google Drive": "Google Drive",
|
||||
"Invert Y-Axis": "Invert Y-Axis",
|
||||
|
@ -165,6 +171,7 @@
|
|||
"Motion GUI": "Motion GUI",
|
||||
"Motion": "Motion",
|
||||
"Global Detector Settings": "Global Detector Settings",
|
||||
"Detector Settings": "Detector Settings",
|
||||
"Trigger Group to Record": "Trigger Group to Record",
|
||||
"Trigger Camera Groups": "Trigger Camera Groups",
|
||||
"Motion Detection": "Motion Detection",
|
||||
|
@ -207,6 +214,7 @@
|
|||
"Videos": "Videos",
|
||||
"Events": "Events",
|
||||
"Events Found": "Events Found",
|
||||
"Objects Found": "Objects Found",
|
||||
"Recent Events": "Recent Events",
|
||||
"Streams": "Streams",
|
||||
"Snapshot": "Snapshot",
|
||||
|
@ -254,6 +262,9 @@
|
|||
"Can Edit Monitor": "Can Edit Monitor",
|
||||
"Can Delete Videos": "Can Delete Videos",
|
||||
"Delete Video": "Delete Video",
|
||||
"Delete Videos": "Delete Videos",
|
||||
"Batch Download": "Batch Download",
|
||||
"batchDownloadText": "Do you want to download the selected files?",
|
||||
"Delete Timelapse Frame": "Delete Timelapse Frame",
|
||||
"Can View Videos and Events": "Can View Videos and Events",
|
||||
"Can Delete Videos and Events": "Can Delete Videos and Events",
|
||||
|
@ -384,6 +395,7 @@
|
|||
"yourFileDownloadedShortly": "Please wait. Your file will be downloaded shortly.",
|
||||
"Save Built Video on Completion": "Save Built Video on Completion",
|
||||
"FileBin Share": "FileBin Share",
|
||||
"Timelapse Frames": "Timelapse Frames",
|
||||
"Timelapse Frames Share": "Timelapse Frames Share",
|
||||
"Timelapse Frames Video": "Timelapse Frames Video",
|
||||
"Number of Days to keep": "Number of Days to keep",
|
||||
|
@ -480,9 +492,10 @@
|
|||
"Delete Filter": "Delete Filter",
|
||||
"confirmDeleteFilter": "Do you want to delete this filter? You cannot recover it.",
|
||||
"Fix Video": "Fix Video",
|
||||
"FixVideoMsg": "Do you want to fix this video? You cannot undo this action.",
|
||||
"FixVideoMsg": "Do you want to fix this video? This will create a new file and overwrite the old one. You cannot undo this action.",
|
||||
"DeleteVideoMsg": "Do you want to delete this video? You cannot recover it.",
|
||||
"DeleteThisMsg": "Do you want to delete this? You cannot recover it.",
|
||||
"DeleteTheseMsg": "Do you want to delete these? You cannot recover them.",
|
||||
"dropBoxSuccess": "Success! Files saved to your Dropbox.",
|
||||
"API Key Deleted": "API Key Deleted",
|
||||
"APIKeyDeletedText": "Key has been deleted. It will no longer work.",
|
||||
|
@ -655,7 +668,7 @@
|
|||
"HLS Preset": "Preset Template",
|
||||
"in seconds": "in seconds",
|
||||
"HLS List Size": "List Size",
|
||||
"Traditional Recording": "Traditional Recording",
|
||||
"Event-Based Recording": "Event-Based Recording",
|
||||
"Recorded Buffer": "Recorded Buffer",
|
||||
"Buffer Preview": "Buffer Preview",
|
||||
"HLS Start Number": "HLS Start Number",
|
||||
|
@ -728,7 +741,7 @@
|
|||
"Stream to YouTube": "Stream to YouTube",
|
||||
"Stream to YouTube Flags": "Stream to YouTube Flags",
|
||||
"Recording Flags": "Recording Flags",
|
||||
"Traditional Recording Flags": "Traditional Recording Flags",
|
||||
"Event-Based Recording Flags": "Event-Based Recording Flags",
|
||||
"Output Method": "Output Method",
|
||||
"Webhook": "Webhook",
|
||||
"Event Webhook Error": "Event Webhook Error",
|
||||
|
@ -738,6 +751,7 @@
|
|||
"Command": "Command",
|
||||
"No Trigger": "No Trigger",
|
||||
"noTriggerText": "If motion has not been detected after the timeout period you will recieve an Discord notification.",
|
||||
"On Event": "On Event",
|
||||
"on Event": "on Event",
|
||||
"Allow Next Alert": "Allow Next Alert",
|
||||
"Allow Next Webhook": "Allow Next Webhook",
|
||||
|
@ -855,9 +869,8 @@
|
|||
"libvorbis (Default)": "libvorbis (Default)",
|
||||
"libopus": "libopus",
|
||||
"aac (Default)": "aac (Default)",
|
||||
"Traditional (Watch-Only, Includes Buffer)": "Traditional (Watch-Only, Includes Buffer)",
|
||||
"Hotswap Modes (Watch-Only)": "Hotswap Modes (Watch-Only)",
|
||||
"Delete Motionless Videos (Record)": "Delete Motionless Videos (Record)",
|
||||
"Event-Based Recording (For Watch-Only Mode)": "Event-Based Recording (For Watch-Only Mode)",
|
||||
"Delete Motionless Videos (For Record Mode)": "Delete Motionless Videos (For Record Mode)",
|
||||
"US": "US",
|
||||
"EU": "EU",
|
||||
"Silent": "Silent",
|
||||
|
@ -958,8 +971,12 @@
|
|||
"updateKeyText1": "\"updateKey\" is missing from \"conf.json\", cannot do updates this way until you add it.",
|
||||
"updateKeyText2": "\"updateKey\" is incorrect.",
|
||||
"Control Trigger Started": "Control Trigger Started",
|
||||
"Control Trigger Ended": "Control Trigger Ended",
|
||||
"Control Triggered": "Control Triggered",
|
||||
"Control Error": "Control Error",
|
||||
"Timed": "Timed",
|
||||
"On Release": "On Release",
|
||||
"Moving to Home Preset": "Moving to Home Preset",
|
||||
"Database row does not exist": "Database row does not exist",
|
||||
"File Delete Error": "File Delete Error",
|
||||
"List of Videos Delete Error": "List of Videos Delete Error",
|
||||
|
@ -986,6 +1003,7 @@
|
|||
"No Group with this key exists": "No Group with this key exists",
|
||||
"Downloaded!": "Downloaded!",
|
||||
"Downloading...": "Downloading...",
|
||||
"Loading...": "Loading...",
|
||||
"Downloading Videos": "Downloading Videos",
|
||||
"Zipping Videos": "Zipping Videos",
|
||||
"Automatic Checking Cancelled": "Automatic Checking Cancelled",
|
||||
|
@ -1151,6 +1169,7 @@
|
|||
"RTMP Stream Flags": "RTMP Stream Flags",
|
||||
"RTMP Stream": "RTMP Stream",
|
||||
"Stream Channel": "Stream Channel",
|
||||
"Stream Channels": "Stream Channels",
|
||||
"Confidence": "Confidence",
|
||||
"Trainer Engine": "Trainer Engine",
|
||||
"Train": "Train",
|
||||
|
@ -1170,6 +1189,7 @@
|
|||
"Loop Stream": "Loop Stream",
|
||||
"Object Count": "Object Count",
|
||||
"Object Tag": "Object Tag",
|
||||
"Search Object Tags": "Search Object Tags",
|
||||
"Noise Filter": "Noise Filter",
|
||||
"Noise Filter Range": "Noise Filter Range",
|
||||
"TV Channel": "TV Channel",
|
||||
|
@ -1183,6 +1203,7 @@
|
|||
"Confidence of Detection": "Confidence of Detection",
|
||||
"Edit Selected": "Edit Selected",
|
||||
"Copy Stream Channels": "Copy Stream Channels",
|
||||
"Copy to Selected Monitor(s)": "Copy to Selected Monitor(s)",
|
||||
"Copy Settings": "Copy Settings",
|
||||
"Copy to Settings": "Copy to Settings",
|
||||
"Copy Mode": "Copy Mode",
|
||||
|
@ -1397,6 +1418,9 @@
|
|||
"MQTT Inbound": "MQTT Inbound",
|
||||
"MQTT Outbound": "MQTT Outbound",
|
||||
"MQTT Client": "MQTT Client",
|
||||
"Buffer Time from Event": "Buffer Time from Event",
|
||||
"fieldTextEventFilters": "Enable to have all Events honor your Event Filter rules.",
|
||||
"fieldTextBufferTimeFromEvent": "The amount of seconds to record before the trigger happened. If this is consistently inaccurate you will need to look at the <a target='_blank' href='https://hub.shinobi.video/articles/view/DmWIID78VtvEfnf'>optimization guide</a> or force encoding on the server.",
|
||||
"fieldTextMode": "This is the primary task of the monitor.",
|
||||
"fieldTextModeDisabled": "Inactive monitor, no process will be created in this mode.",
|
||||
"fieldTextModeWatchOnly": "Monitor will only stream, no recording will occur unless otherwise ordered by API or Detector.",
|
||||
|
@ -1563,11 +1587,11 @@
|
|||
"fieldTextDetectorScaleX": "Width of the image being detected. Smaller sizes take less CPU.",
|
||||
"fieldTextDetectorScaleY": "Height of the image being detected. Smaller sizes take less CPU.",
|
||||
"fieldTextDetectorLockTimeout": "Lockout for when the next trigger is allowed, to avoid overloading the database and receiving clients. Measured in milliseconds.",
|
||||
"fieldTextDetectorSave": "Save Motion Events in SQL. This will allow display of motion over video during the time motion events occured in the Power Viewer.",
|
||||
"fieldTextDetectorRecordMethod": "There are multiple ways to begin recording when an event occurs, like motion. Traditional Recording is the most user-friendly.",
|
||||
"fieldTextDetectorSave": "Save Events in the database. This will allow display of the events over video during playback.",
|
||||
"fieldTextDetectorRecordMethod": "There are multiple ways to begin recording when an event occurs, like motion. Event-Based Recording is the most user-friendly.",
|
||||
"fieldTextDetectorTrigger": "This will order the camera to record if it is set to \"Watch-Only\" when an Event is detected.",
|
||||
"fieldTextDetectorTimeout": "The length of time \"Trigger Record\" will run for. This is read in minutes.",
|
||||
"fieldTextWatchdogReset": "If there is an overlap in trigger record should it reset.",
|
||||
"fieldTextWatchdogReset": "Enable to reset the active Recording Timeout back to the beginning when a new event occurs while recording.",
|
||||
"fieldTextDetectorWebhook": "Send a GET request to a URL with some values from the event.",
|
||||
"fieldTextDetectorWebhookTimeout": "This value is a timer to allow the next running of your Webhook. This value is in minutes.",
|
||||
"fieldTextDetectorCommand": "The command that will run. This is the equivalent of running a shell command from terminal.",
|
||||
|
@ -1579,7 +1603,7 @@
|
|||
"fieldTextDetectorThreshold": "Minimum number of detections to fire a motion event. Detections must be within the detector the threshold divided by detector fps seconds. For example, if detector fps is 2 and trigger threshold is 3, then three detections must occur within 1.5 seconds to trigger a motion event. This threshold is per detection region.",
|
||||
"fieldTextDetectorColorThreshold": "The amount of difference allowed in a pixel before it is considered motion.",
|
||||
"fieldTextInverseTrigger": "To trigger outside specified regions. Will not trigger with Full Frame Detection enabled.",
|
||||
"fieldTextDetectorFrame": "This will read the entire frame for pixel differences. This is the same as creating a region that covers the entire screen.",
|
||||
"fieldTextDetectorFrame": "This will read the entire frame for pixel differences. This is the same as creating a region that covers the entire screen. If no Region is added to this Monitor this option will default to Yes.",
|
||||
"fieldTextDetectorNoiseFilter": "Attempt to filter grain or repeated motion at a particular indifference.",
|
||||
"fieldTextDetectorNoiseFilterRange": "The amount of difference allowed in a pixel before it is considered motion.",
|
||||
"fieldTextDetectorNotrigger": "Check if motion has occured on an interval. If motion has occurred the check will be reset.",
|
||||
|
@ -1633,7 +1657,7 @@
|
|||
"fieldTextActionsHalt": "Make the event do nothing, as if it never happened.",
|
||||
"fieldTextActionsIndifference": "Modify minimum indifference required for event.",
|
||||
"fieldTextActionsCommand": "You may use this to trigger a script on command.",
|
||||
"fieldTextActionsRecord": "Use Traditional Recording, Hotswap, or Delete Motionless with their currently set options in the Global Detection Settings section.",
|
||||
"fieldTextActionsRecord": "Use Event-Based Recording, Hotswap, or Delete Motionless with their currently set options in the Global Detection Settings section.",
|
||||
"fieldTextMapRtspTransportAuto": "Let FFMPEG decide. Normally it will try UDP first.",
|
||||
"fieldTextMapRtspTransportTCP": "Set it to this if UDP starts giving undesired results.",
|
||||
"fieldTextMapRtspTransportUDP": "FFMPEG tries this first.",
|
||||
|
|
33
libs/auth.js
33
libs/auth.js
|
@ -78,24 +78,21 @@ module.exports = function(s,config,lang){
|
|||
var isSessionKey = false
|
||||
if(apiKey){
|
||||
var sessionKey = params.auth
|
||||
createSession(apiKey,{
|
||||
auth: sessionKey,
|
||||
permissions: s.parseJSON(apiKey.details),
|
||||
details: {}
|
||||
})
|
||||
getUserByUid(apiKey,'mail,details',function(err,user){
|
||||
if(user){
|
||||
try{
|
||||
editSession({
|
||||
auth: sessionKey
|
||||
},{
|
||||
mail: user.mail,
|
||||
details: s.parseJSON(user.details),
|
||||
lang: s.getLanguageFile(user.details.lang)
|
||||
})
|
||||
}catch(er){
|
||||
console.log('FAILED TO EDIT',er)
|
||||
}
|
||||
createSession(apiKey,{
|
||||
auth: sessionKey,
|
||||
permissions: s.parseJSON(apiKey.details),
|
||||
mail: user.mail,
|
||||
details: s.parseJSON(user.details),
|
||||
lang: s.getLanguageFile(user.details.lang)
|
||||
})
|
||||
}else{
|
||||
createSession(apiKey,{
|
||||
auth: sessionKey,
|
||||
permissions: s.parseJSON(apiKey.details),
|
||||
details: {}
|
||||
})
|
||||
}
|
||||
callback(err,s.api[params.auth])
|
||||
})
|
||||
|
@ -132,9 +129,7 @@ module.exports = function(s,config,lang){
|
|||
var editSession = function(user,additionalData){
|
||||
if(user){
|
||||
if(!additionalData)additionalData = {}
|
||||
Object.keys(additionalData).forEach(function(value,key){
|
||||
s.api[user.auth][key] = value
|
||||
})
|
||||
Object.assign(s.api[user.auth], additionalData)
|
||||
}
|
||||
}
|
||||
var failHttpAuthentication = function(res,req,message){
|
||||
|
|
|
@ -186,6 +186,13 @@ module.exports = (processCwd,config) => {
|
|||
else
|
||||
return false;
|
||||
}
|
||||
function asyncSetTimeout(timeoutAmount) {
|
||||
return new Promise((resolve,reject) => {
|
||||
setTimeout(function(){
|
||||
resolve()
|
||||
},timeoutAmount)
|
||||
})
|
||||
}
|
||||
return {
|
||||
parseJSON: parseJSON,
|
||||
stringJSON: stringJSON,
|
||||
|
@ -203,5 +210,6 @@ module.exports = (processCwd,config) => {
|
|||
fetchTimeout: fetchTimeout,
|
||||
fetchDownloadAndWrite: fetchDownloadAndWrite,
|
||||
fetchWithAuthentication: fetchWithAuthentication,
|
||||
asyncSetTimeout: asyncSetTimeout,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
if(!config.logoLocation196x196)config.logoLocation196x196 = 'libs/img/icon/favicon-196x196.png';
|
||||
if(config.logoLocation76x76Link === undefined){config.logoLocation76x76Link='https://shinobi.video'}
|
||||
if(config.logoLocation76x76Style === undefined){config.logoLocation76x76Style='border-radius:50%'}
|
||||
if(config.loginScreenBackground === undefined){config.loginScreenBackground='https://shinobi.video/libs/assets/backgrounds/7.jpg'}
|
||||
if(config.loginScreenBackground === undefined){config.loginScreenBackground='assets/img/splash.avif'}
|
||||
if(config.showLoginSelector === undefined){config.showLoginSelector=true}
|
||||
if(config.defaultTheme === undefined)config.defaultTheme = 'Ice-v3';
|
||||
if(config.socialLinks === undefined){
|
||||
|
|
|
@ -1,30 +1,36 @@
|
|||
module.exports = function(jsonData,pamDiffResponder){
|
||||
const {
|
||||
// see libs/detectorUtils.js for more parameters and functions
|
||||
//
|
||||
config,
|
||||
completeMonitorConfig,
|
||||
groupKey,
|
||||
monitorId,
|
||||
monitorName,
|
||||
monitorDetails,
|
||||
completeMonitorConfig,
|
||||
} = require('./libs/monitorUtils.js')(jsonData)
|
||||
const {
|
||||
convertRegionsToTiles,
|
||||
} = require('./libs/tileCutter.js')
|
||||
const loadDetectorUtils = require('./libs/detectorUtils.js')
|
||||
let detectorUtils
|
||||
let onMotionData = null
|
||||
if(monitorDetails.detector_motion_tile_mode === '1'){
|
||||
const {
|
||||
originalCords,
|
||||
newRegionsBySquares,
|
||||
} = convertRegionsToTiles(monitorDetails)
|
||||
jsonData.rawMonitorConfig.details.cords = newRegionsBySquares;
|
||||
detectorUtils = loadDetectorUtils(jsonData,pamDiffResponder)
|
||||
detectorUtils.originalCords = originalCords;
|
||||
onMotionData = detectorUtils.getTileMotionEvent()
|
||||
}else{
|
||||
detectorUtils = loadDetectorUtils(jsonData,pamDiffResponder)
|
||||
}
|
||||
const {
|
||||
pamDetectorIsEnabled,
|
||||
//
|
||||
attachPamPipeDrivers,
|
||||
//
|
||||
getAcceptedTriggers,
|
||||
getRegionsWithMinimumChange,
|
||||
getRegionsBelowMaximumChange,
|
||||
getRegionsWithThresholdMet,
|
||||
filterTheNoise,
|
||||
filterTheNoiseFromMultipleRegions,
|
||||
//
|
||||
buildDetectorObject,
|
||||
buildTriggerEvent,
|
||||
sendDetectedData,
|
||||
} = require('./libs/detectorUtils.js')(jsonData,pamDiffResponder)
|
||||
} = detectorUtils;
|
||||
return function(cameraProcess){
|
||||
if(pamDetectorIsEnabled){
|
||||
attachPamPipeDrivers(cameraProcess)
|
||||
attachPamPipeDrivers(cameraProcess,onMotionData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
const P2P = require('pipe2pam')
|
||||
let PamDiff = require('pam-diff')
|
||||
const {
|
||||
makeBigMatricesFromSmallOnes,
|
||||
} = require('./tileCutter.js')
|
||||
module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
|
||||
if(alternatePamDiff)PamDiff = alternatePamDiff;
|
||||
const noiseFilterArray = {};
|
||||
|
@ -71,7 +74,7 @@ module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
|
|||
var sendDetectedData = function(detectorObject){
|
||||
pamDiffResponder(detectorObject)
|
||||
}
|
||||
}else{
|
||||
}else{
|
||||
var sendDetectedData = function(detectorObject){
|
||||
pamDiffResponder.write(Buffer.from(JSON.stringify(detectorObject)))
|
||||
}
|
||||
|
@ -241,6 +244,28 @@ module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
|
|||
pamDiff.on('diff',pamAnalyzer)
|
||||
cameraProcess.stdio[3].pipe(p2p).pipe(pamDiff)
|
||||
}
|
||||
function getTileMotionEvent(){
|
||||
let pamAnalyzer = function(){}
|
||||
if(monitorDetails.detector_noise_filter === '1'){
|
||||
pamAnalyzer = async (data) => {
|
||||
const acceptedTriggers = getAcceptedTriggers(data.trigger)
|
||||
const passedFilter = await filterTheNoiseFromMultipleRegions(acceptedTriggers)
|
||||
if(passedFilter){
|
||||
const mergedTriggers = mergePamTriggers(acceptedTriggers)
|
||||
mergedTriggers.matrices = makeBigMatricesFromSmallOnes(mergedTriggers.matrices)
|
||||
buildTriggerEvent(mergedTriggers)
|
||||
}
|
||||
}
|
||||
}else{
|
||||
pamAnalyzer = (data) => {
|
||||
const acceptedTriggers = getAcceptedTriggers(data.trigger)
|
||||
const mergedTriggers = mergePamTriggers(acceptedTriggers)
|
||||
mergedTriggers.matrices = makeBigMatricesFromSmallOnes(mergedTriggers.matrices)
|
||||
buildTriggerEvent(mergedTriggers)
|
||||
}
|
||||
}
|
||||
return pamAnalyzer
|
||||
}
|
||||
function createPamDiffRegionArray(regions,globalColorThreshold,globalSensitivity,fullFrame){
|
||||
var pamDiffCompliantArray = [],
|
||||
arrayForOtherStuff = [],
|
||||
|
@ -372,6 +397,7 @@ module.exports = function(jsonData,pamDiffResponder,alternatePamDiff){
|
|||
getPropertiesFromBlob,
|
||||
createMatricesFromBlobs,
|
||||
logData,
|
||||
getTileMotionEvent,
|
||||
// parameters
|
||||
pamDetectorIsEnabled,
|
||||
noiseFilterArray,
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
module.exports = (jsonData) => {
|
||||
const completeMonitorConfig = jsonData.rawMonitorConfig
|
||||
const groupKey = completeMonitorConfig.ke
|
||||
const monitorId = completeMonitorConfig.mid
|
||||
const monitorName = completeMonitorConfig.name
|
||||
const monitorDetails = completeMonitorConfig.details
|
||||
return {
|
||||
completeMonitorConfig,
|
||||
groupKey,
|
||||
monitorId,
|
||||
monitorName,
|
||||
monitorDetails,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
const SAT = require('sat')
|
||||
const V = SAT.Vector;
|
||||
const P = SAT.Polygon;
|
||||
const B = SAT.Box;
|
||||
|
||||
function intersectionY(edge, y) {
|
||||
const [[x1, y1], [x2, y2]] = edge;
|
||||
const dir = Math.sign(y2 - y1);
|
||||
if (dir && (y1 - y)*(y2 - y) <= 0) return { x: x1 + (y-y1)/(y2-y1) * (x2-x1), dir };
|
||||
}
|
||||
|
||||
|
||||
function tilePolygon(points, tileSize){
|
||||
// https://stackoverflow.com/questions/56827208/spilliting-polygon-into-square
|
||||
const minY = Math.min(...points.map(p => p[1]));
|
||||
const maxY = Math.max(...points.map(p => p[1]));
|
||||
const minX = Math.min(...points.map(p => p[0]));
|
||||
const gridPoints = [];
|
||||
for (let y = minY; y <= maxY; y += tileSize) {
|
||||
// Collect x-coordinates where polygon crosses this horizontal line (y)
|
||||
const cuts = [];
|
||||
let prev = null;
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const cut = intersectionY([points[i], points[(i+1)%points.length]], y);
|
||||
if (!cut) continue;
|
||||
if (!prev || prev.dir !== cut.dir) cuts.push(cut);
|
||||
prev = cut;
|
||||
}
|
||||
if (prev && prev.dir === cuts[0].dir) cuts.pop();
|
||||
// Now go through those cuts from left to right toggling whether we are in/out the polygon
|
||||
let dirSum = 0;
|
||||
let startX = null;
|
||||
for (let cut of cuts.sort((a, b) => a.x - b.x)) {
|
||||
dirSum += cut.dir;
|
||||
if (dirSum % 2) { // Entering polygon
|
||||
if (startX === null) startX = cut.x;
|
||||
} else if (startX !== null) { // Exiting polygon
|
||||
// Genereate grid points on this horizontal line segement
|
||||
for (let x = minX + Math.ceil((startX - minX) / tileSize)*tileSize; x <= cut.x; x += tileSize) {
|
||||
gridPoints.push([x, y]);
|
||||
}
|
||||
startX = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return gridPoints;
|
||||
}
|
||||
|
||||
function convertStringPoints(oldPoints){
|
||||
// [["0","0"],["0","150"],["300","150"],["300","0"]]
|
||||
var newPoints = []
|
||||
oldPoints.forEach((point) => {
|
||||
newPoints.push([parseInt(point[0]),parseInt(point[1])])
|
||||
})
|
||||
return newPoints
|
||||
}
|
||||
|
||||
function createSquares(gridPoints,imgWidth,imgHeight){
|
||||
var rows = [];
|
||||
var n = 0;
|
||||
var curentLine = gridPoints[0][1]
|
||||
gridPoints.forEach((point) => {
|
||||
if(!rows[n])rows[n] = []
|
||||
rows[n].push(point)
|
||||
if(curentLine !== point[1]){
|
||||
curentLine = point[1];
|
||||
++n;
|
||||
}
|
||||
});
|
||||
var squares = [];
|
||||
rows.forEach((row,n) => {
|
||||
for (let i = 0; i < row.length; i += 2) {
|
||||
if(!rows[n + 1] || !row[i + 1])return;
|
||||
var square = [row[i],row[i + 1],rows[n + 1][i],rows[n + 1][i + 1]]
|
||||
squares.push(square)
|
||||
}
|
||||
})
|
||||
return squares
|
||||
}
|
||||
const getAllSquaresTouchingRegion = function(region,squares){
|
||||
var matrixPoints = []
|
||||
var collisions = []
|
||||
var polyPoints = []
|
||||
region.points.forEach(function(point){
|
||||
polyPoints.push(new V(parseInt(point[0]),parseInt(point[1])))
|
||||
})
|
||||
var regionPoly = new P(new V(0,0), polyPoints)
|
||||
squares.forEach(function(squarePoints){
|
||||
var firstPoint = squarePoints[0]
|
||||
var thirdPoint = squarePoints[2]
|
||||
var squareX = firstPoint[0]
|
||||
var squareY = firstPoint[1]
|
||||
var squareWidth = thirdPoint[0] - firstPoint[0]
|
||||
var squareHeight = thirdPoint[1] - firstPoint[1]
|
||||
var squarePoly = new B(new V(squareX, squareY), squareWidth, squareHeight).toPolygon()
|
||||
var response = new SAT.Response()
|
||||
var collided = SAT.testPolygonPolygon(squarePoly, regionPoly, response)
|
||||
if(collided === true){
|
||||
collisions.push(squarePoints)
|
||||
}
|
||||
})
|
||||
return collisions
|
||||
}
|
||||
function makeBigMatricesFromSmallOnes(matrices){
|
||||
var bigMatrices = {}
|
||||
matrices.forEach(function(matrix,n){
|
||||
const regionName = matrix.tag
|
||||
if(!bigMatrices[regionName]){
|
||||
bigMatrices[regionName] = {
|
||||
tag: regionName,
|
||||
x: 9999999999,
|
||||
y: 9999999999,
|
||||
width: matrices.length > 1 ? matrices[0].width : 0,
|
||||
height: matrices.length > 1 ? matrices[0].height : 0,
|
||||
tilesCounted: 0,
|
||||
confidence: 0,
|
||||
}
|
||||
}
|
||||
var bigMatrix = bigMatrices[regionName];
|
||||
bigMatrix.x = bigMatrix.x > matrix.x ? matrix.x : bigMatrix.x;
|
||||
bigMatrix.y = bigMatrix.y > matrix.y ? matrix.y : bigMatrix.y;
|
||||
const newWidth = matrix.x - bigMatrix.x
|
||||
const newHeight = matrix.y - bigMatrix.y
|
||||
bigMatrix.width = bigMatrix.width < matrix.x ? newWidth === 0 ? matrix.width : newWidth : bigMatrix.width;
|
||||
bigMatrix.height = bigMatrix.height < matrix.y ? newHeight === 0 ? matrix.height : newHeight : bigMatrix.height;
|
||||
// bigMatrix.tag = matrix.tag;
|
||||
bigMatrix.confidence += matrix.confidence;
|
||||
bigMatrix.tilesCounted += 1;
|
||||
})
|
||||
let allBigMatrices = Object.values(bigMatrices)
|
||||
allBigMatrices.forEach(function(matrix,n){
|
||||
let bigMatrix = allBigMatrices[n]
|
||||
bigMatrix.averageConfidence = bigMatrix.confidence / bigMatrix.tilesCounted;
|
||||
})
|
||||
return allBigMatrices
|
||||
}
|
||||
function convertRegionsToTiles(monitorDetails){
|
||||
let originalCords;
|
||||
//force full frame detection to be use for tracking blobs
|
||||
monitorDetails.detector_frame = '1'
|
||||
monitorDetails.detector_sensitivity = '1'
|
||||
monitorDetails.detector_color_threshold = monitorDetails.detector_color_threshold || '7'
|
||||
try{
|
||||
monitorDetails.cords = JSON.parse(monitorDetails.cords)
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
originalCords = Object.values(monitorDetails.cords)
|
||||
const regionKeys = Object.keys(monitorDetails.cords);
|
||||
const newRegionsBySquares = {}
|
||||
try{
|
||||
regionKeys.forEach(function(regionKey){
|
||||
const region = monitorDetails.cords[regionKey]
|
||||
const tileSize = parseInt(region.detector_tile_size) || 20;
|
||||
const gridPoints = tilePolygon([
|
||||
[0,0],
|
||||
[0,height],
|
||||
[width,height],
|
||||
[width,0]
|
||||
],tileSize)
|
||||
const squares = createSquares(gridPoints,width,height)
|
||||
const squaresInRegion = getAllSquaresTouchingRegion(region,squares)
|
||||
squaresInRegion.forEach((square,n) => {
|
||||
newRegionsBySquares[`${regionKey}_${n}`] = Object.assign({},region,{
|
||||
"points": square
|
||||
})
|
||||
})
|
||||
})
|
||||
// jsonData.rawMonitorConfig.details.cords = newRegionsBySquares;
|
||||
}catch(err){
|
||||
process.logData(err)
|
||||
}
|
||||
// detectorUtils.originalCords = originalCords;
|
||||
return {
|
||||
originalCords,
|
||||
newRegionsBySquares,
|
||||
}
|
||||
}
|
||||
module.exports = {
|
||||
tilePolygon,
|
||||
createSquares,
|
||||
convertRegionsToTiles,
|
||||
getAllSquaresTouchingRegion,
|
||||
makeBigMatricesFromSmallOnes,
|
||||
}
|
|
@ -258,8 +258,9 @@ function startConnection(p2pServerAddress,subscriptionId){
|
|||
})
|
||||
onIncomingMessage('disconnect',function(data,requestId){
|
||||
console.log(`FAILED LICENSE CHECK ON P2P`)
|
||||
if(data.retryLater)console.log(`Retrying Later`)
|
||||
stayDisconnected = data && !data.retryLater
|
||||
const retryLater = data && data.retryLater;
|
||||
stayDisconnected = !retryLater
|
||||
if(retryLater)console.log(`Retrying P2P Later...`)
|
||||
})
|
||||
}
|
||||
const responseTunnels = {}
|
||||
|
|
|
@ -26,13 +26,20 @@ const getBuffer = async (url) => {
|
|||
try {
|
||||
const response = await fetch(url);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const buffer = Buffer.from(arrayBuffer);
|
||||
return buffer;
|
||||
return Buffer.from(arrayBuffer);
|
||||
} catch (error) {
|
||||
return { error };
|
||||
}
|
||||
};
|
||||
function addCredentialsToUrl(options){
|
||||
const streamUrl = options.url
|
||||
const username = options.username
|
||||
const password = options.password
|
||||
const urlParts = streamUrl.split('://')
|
||||
return [urlParts[0],'://',`${username}:${password}@`,urlParts[1]].join('')
|
||||
}
|
||||
module.exports = {
|
||||
addCredentialsToUrl,
|
||||
getBuffer: getBuffer,
|
||||
mergeDeep: mergeDeep,
|
||||
validateIntValue: (value) => {
|
||||
|
|
|
@ -1,7 +1,35 @@
|
|||
var os = require('os');
|
||||
var exec = require('child_process').exec;
|
||||
module.exports = function(s,config,lang,app,io){
|
||||
const {
|
||||
startMove,
|
||||
stopMove,
|
||||
ptzControl,
|
||||
} = require('./control/ptz.js')(s,config,lang)
|
||||
require('./control/onvif.js')(s,config,lang,app,io)
|
||||
require('./control/zwave.js')(s,config,lang,app,io)
|
||||
// const ptz = require('./control/ptz.js')(s,config,lang,app,io)
|
||||
s.onOtherWebSocketMessages((data,connection) => {
|
||||
switch(data.f){
|
||||
case'startMove':
|
||||
startMove(data).then((response) => {
|
||||
s.debugLog(response)
|
||||
})
|
||||
break;
|
||||
case'stopMove':
|
||||
stopMove(data).then((response) => {
|
||||
s.debugLog(response)
|
||||
})
|
||||
break;
|
||||
case'control':
|
||||
ptzControl(data,function(msg){
|
||||
s.userLog(data,msg);
|
||||
connection.emit('f',{
|
||||
f: 'control',
|
||||
response: msg
|
||||
})
|
||||
})
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
var os = require('os');
|
||||
var exec = require('child_process').exec;
|
||||
const onvif = require("shinobi-onvif");
|
||||
const {
|
||||
addCredentialsToUrl,
|
||||
} = require('../common.js')
|
||||
module.exports = function(s,config,lang,app,io){
|
||||
const {
|
||||
createSnapshot,
|
||||
|
@ -128,9 +131,13 @@ module.exports = function(s,config,lang,app,io){
|
|||
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;
|
||||
theUrl = addCredentialsToUrl({
|
||||
username: onvifOptions.username,
|
||||
password: onvifOptions.password,
|
||||
url: (await theDevice.services.media.getSnapshotUri({
|
||||
ProfileToken : theDevice.current_profile.token,
|
||||
})).GetSnapshotUriResponse.MediaUri.Uri
|
||||
});
|
||||
}else{
|
||||
theUrl = addCredentialsToStreamLink({
|
||||
username: onvifOptions.username,
|
||||
|
|
|
@ -1,286 +1,299 @@
|
|||
var os = require('os');
|
||||
var exec = require('child_process').exec;
|
||||
module.exports = function(s,config,lang){
|
||||
const { fetchWithAuthentication } = require('../basic/utils.js')(process.cwd(),config)
|
||||
const { fetchWithAuthentication, asyncSetTimeout } = require('../basic/utils.js')(process.cwd(),config)
|
||||
const moveLock = {}
|
||||
const ptzTimeoutsUntilResetToHome = {}
|
||||
const sliceUrlAuth = (url) => {
|
||||
return /^(.+?\/\/)(?:.+?:.+?@)?(.+)$/.exec(url).slice(1).join('')
|
||||
}
|
||||
const startMove = async function(options,callback){
|
||||
const device = s.group[options.ke].activeMonitors[options.id].onvifConnection
|
||||
if(!device){
|
||||
function getGenericControlParameters(optionsm,urlType){
|
||||
const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
|
||||
const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
|
||||
const controlBaseUrl = monitorConfig.details.control_base_url || s.buildMonitorUrl(monitorConfig, true)
|
||||
let theURL;
|
||||
if(urlType === 'start'){
|
||||
theURL = controlBaseUrl + monitorConfig.details[`control_url_${options.direction}`]
|
||||
}else{
|
||||
theURL = controlBaseUrl + monitorConfig.details[`control_url_${options.direction}_stop`]
|
||||
}
|
||||
let controlOptions = s.cameraControlOptionsFromUrl(theURL,monitorConfig);
|
||||
const hasDigestAuthEnabled = monitorConfig.details.control_digest_auth === '1'
|
||||
const requestUrl = controlBaseUrl + controlOptions.path
|
||||
return {
|
||||
monitorConfig,
|
||||
controlUrlMethod,
|
||||
controlBaseUrl,
|
||||
theURL,
|
||||
controlOptions,
|
||||
hasDigestAuthEnabled,
|
||||
requestUrl,
|
||||
}
|
||||
}
|
||||
function moveGeneric(options,doStart){
|
||||
if(!s.group[options.ke] || !s.group[options.ke].activeMonitors[options.id]){return}
|
||||
return new Promise((resolve,reject) => {
|
||||
if(doStart)moveLock[options.ke + options.id] = true;
|
||||
const {
|
||||
monitorConfig,
|
||||
controlUrlMethod,
|
||||
controlBaseUrl,
|
||||
theURL,
|
||||
controlOptions,
|
||||
hasDigestAuthEnabled,
|
||||
requestUrl,
|
||||
} = getGenericControlParameters(options,'start')
|
||||
const response = {
|
||||
ok: true,
|
||||
type: lang[doStart ? 'Control Triggered' : 'Control Trigger Ended']
|
||||
}
|
||||
const theRequest = fetchWithAuthentication(requestUrl,{
|
||||
method: controlUrlMethod || controlOptions.method,
|
||||
digestAuth: hasDigestAuthEnabled,
|
||||
body: controlOptions.postData || null
|
||||
});
|
||||
theRequest.then(res => res.text())
|
||||
.then((data) => {
|
||||
if(doStart){
|
||||
const stopCommandEnabled = monitorConfig.details.control_stop === '1' || monitorConfig.details.control_stop === '2';
|
||||
if(stopCommandEnabled && options.direction !== 'center'){
|
||||
s.userLog(monitorConfig,{type: lang['Control Trigger Started']});
|
||||
}else{
|
||||
moveLock[options.ke + options.id] = false
|
||||
s.userLog(monitorConfig,response);
|
||||
}
|
||||
}else{
|
||||
moveLock[options.ke + options.id] = false
|
||||
s.userLog(monitorConfig,response);
|
||||
}
|
||||
resolve(response)
|
||||
});
|
||||
theRequest.catch((err) => {
|
||||
response.ok = false
|
||||
response.type = lang['Control Error']
|
||||
response.msg = err
|
||||
resolve(response)
|
||||
})
|
||||
})
|
||||
}
|
||||
function getOnvifControlOptions(options){
|
||||
const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
|
||||
const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
|
||||
const turnSpeed = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
|
||||
const controlOptions = {
|
||||
Velocity : {}
|
||||
}
|
||||
if(options.axis){
|
||||
options.axis.forEach((axis) => {
|
||||
controlOptions.Velocity[axis.direction] = axis.amount < 0 ? -turnSpeed : axis.amount > 0 ? turnSpeed : 0
|
||||
})
|
||||
}else{
|
||||
const onvifDirections = {
|
||||
"left": [-turnSpeed,'x'],
|
||||
"right": [turnSpeed,'x'],
|
||||
"down": [invertedVerticalAxis ? turnSpeed : -turnSpeed,'y'],
|
||||
"up": [invertedVerticalAxis ? -turnSpeed : turnSpeed,'y'],
|
||||
"zoom_in": [turnSpeed,'z'],
|
||||
"zoom_out": [-turnSpeed,'z']
|
||||
}
|
||||
const direction = onvifDirections[options.direction]
|
||||
controlOptions.Velocity[direction[1]] = direction[0]
|
||||
}
|
||||
(['x','y','z']).forEach(function(axis){
|
||||
if(!controlOptions.Velocity[axis])
|
||||
controlOptions.Velocity[axis] = 0
|
||||
})
|
||||
return controlOptions
|
||||
}
|
||||
async function startMoveOnvif(options,callback){
|
||||
const controlOptions = getOnvifControlOptions(options);
|
||||
let activeMonitor = s.group[options.ke].activeMonitors[options.id]
|
||||
let device = activeMonitor.onvifConnection
|
||||
if(
|
||||
!device ||
|
||||
!device.current_profile ||
|
||||
!device.current_profile.token
|
||||
){
|
||||
const response = await s.createOnvifDevice({
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
})
|
||||
const device = s.group[options.ke].activeMonitors[options.id].onvifConnection
|
||||
device = activeMonitor.onvifConnection
|
||||
}
|
||||
options.controlOptions.ProfileToken = device.current_profile.token
|
||||
s.runOnvifMethod({
|
||||
auth: {
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
action: 'continuousMove',
|
||||
service: 'ptz',
|
||||
},
|
||||
options: options.controlOptions,
|
||||
},callback)
|
||||
function returnResponse(){
|
||||
return new Promise((resolve,reject) => {
|
||||
controlOptions.ProfileToken = device.current_profile.token
|
||||
s.runOnvifMethod({
|
||||
auth: {
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
action: 'continuousMove',
|
||||
service: 'ptz',
|
||||
},
|
||||
options: controlOptions,
|
||||
},resolve)
|
||||
})
|
||||
}
|
||||
return await returnResponse();
|
||||
}
|
||||
const stopMove = function(options,callback){
|
||||
const device = s.group[options.ke].activeMonitors[options.id].onvifConnection
|
||||
try{
|
||||
function stopMoveOnvif(options){
|
||||
return new Promise((resolve,reject) => {
|
||||
const device = s.group[options.ke].activeMonitors[options.id].onvifConnection
|
||||
try{
|
||||
s.runOnvifMethod({
|
||||
auth: {
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
action: 'stop',
|
||||
service: 'ptz',
|
||||
},
|
||||
options: {
|
||||
'PanTilt': true,
|
||||
'Zoom': true,
|
||||
ProfileToken: device.current_profile.token
|
||||
},
|
||||
},resolve)
|
||||
}catch(err){
|
||||
resolve({ok: false})
|
||||
}
|
||||
})
|
||||
}
|
||||
function relativeMoveOnvif(options){
|
||||
return new Promise((resolve,reject) => {
|
||||
const controlOptions = getOnvifControlOptions(options);
|
||||
controlOptions.Speed = {'x': 1, 'y': 1, 'z': 1}
|
||||
controlOptions.Translation = Object.assign(controlOptions.Velocity,{})
|
||||
delete(controlOptions.Velocity)
|
||||
moveLock[options.ke + options.id] = true
|
||||
s.runOnvifMethod({
|
||||
auth: {
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
action: 'stop',
|
||||
action: 'relativeMove',
|
||||
service: 'ptz',
|
||||
},
|
||||
options: {
|
||||
'PanTilt': true,
|
||||
'Zoom': true,
|
||||
ProfileToken: device.current_profile.token
|
||||
},
|
||||
},callback)
|
||||
}catch(err){
|
||||
callback({ok: false})
|
||||
}
|
||||
}
|
||||
const moveOnvifCamera = function(options,callback){
|
||||
const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
|
||||
const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
|
||||
const turnSpeed = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
|
||||
const controlUrlStopTimeout = parseInt(monitorConfig.details.control_url_stop_timeout) || 1000
|
||||
switch(options.direction){
|
||||
case'center':
|
||||
moveLock[options.ke + options.id] = true
|
||||
moveToPresetPosition({
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
},(endData) => {
|
||||
moveLock[options.ke + options.id] = false
|
||||
callback({type:'Moving to Home Preset', response: endData})
|
||||
})
|
||||
break;
|
||||
case'stopMove':
|
||||
callback({type:'Control Trigger Ended'})
|
||||
stopMove({
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
},(response) => {
|
||||
moveLock[options.ke + options.id] = false
|
||||
})
|
||||
break;
|
||||
default:
|
||||
try{
|
||||
var controlOptions = {
|
||||
Velocity : {}
|
||||
}
|
||||
if(options.axis){
|
||||
options.axis.forEach((axis) => {
|
||||
controlOptions.Velocity[axis.direction] = axis.amount < 0 ? -turnSpeed : axis.amount > 0 ? turnSpeed : 0
|
||||
})
|
||||
options: controlOptions,
|
||||
},(response) => {
|
||||
if(response.ok){
|
||||
resolve({type: 'Control Triggered'})
|
||||
}else{
|
||||
var onvifDirections = {
|
||||
"left": [-turnSpeed,'x'],
|
||||
"right": [turnSpeed,'x'],
|
||||
"down": [invertedVerticalAxis ? turnSpeed : -turnSpeed,'y'],
|
||||
"up": [invertedVerticalAxis ? -turnSpeed : turnSpeed,'y'],
|
||||
"zoom_in": [turnSpeed,'z'],
|
||||
"zoom_out": [-turnSpeed,'z']
|
||||
}
|
||||
var direction = onvifDirections[options.direction]
|
||||
controlOptions.Velocity[direction[1]] = direction[0]
|
||||
resolve({type: 'Control Triggered', error: response.error})
|
||||
}
|
||||
(['x','y','z']).forEach(function(axis){
|
||||
if(!controlOptions.Velocity[axis])
|
||||
controlOptions.Velocity[axis] = 0
|
||||
})
|
||||
if(monitorConfig.details.control_stop === '1'){
|
||||
moveLock[options.ke + options.id] = false
|
||||
})
|
||||
})
|
||||
}
|
||||
function moveOnvifCamera(options,doMove){
|
||||
return new Promise((resolve,reject) => {
|
||||
const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
|
||||
const controlUrlStopTimeout = parseInt(monitorConfig.details.control_url_stop_timeout) || 1000
|
||||
options.direction = doMove ? options.direction : 'stopMove';
|
||||
switch(options.direction){
|
||||
case'center':
|
||||
moveLock[options.ke + options.id] = true
|
||||
startMove({
|
||||
moveToPresetPosition({
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
controlOptions: controlOptions
|
||||
},(response) => {
|
||||
if(response.ok){
|
||||
if(controlUrlStopTimeout != '0'){
|
||||
setTimeout(function(){
|
||||
stopMove({
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
},(response) => {
|
||||
if(!response.ok){
|
||||
s.systemLog(response)
|
||||
}
|
||||
moveLock[options.ke + options.id] = false
|
||||
})
|
||||
callback({type: 'Control Triggered'})
|
||||
},controlUrlStopTimeout)
|
||||
}
|
||||
}else{
|
||||
s.debugLog(response)
|
||||
}
|
||||
},(endData) => {
|
||||
moveLock[options.ke + options.id] = false
|
||||
resolve({ type: lang['Moving to Home Preset'], response: endData })
|
||||
})
|
||||
}else{
|
||||
controlOptions.Speed = {'x': 1, 'y': 1, 'z': 1}
|
||||
controlOptions.Translation = Object.assign(controlOptions.Velocity,{})
|
||||
delete(controlOptions.Velocity)
|
||||
moveLock[options.ke + options.id] = true
|
||||
s.runOnvifMethod({
|
||||
auth: {
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
action: 'relativeMove',
|
||||
service: 'ptz',
|
||||
},
|
||||
options: controlOptions,
|
||||
},(response) => {
|
||||
if(response.ok){
|
||||
callback({type: 'Control Triggered'})
|
||||
}else{
|
||||
callback({type: 'Control Triggered', error: response.error})
|
||||
}
|
||||
break;
|
||||
case'stopMove':
|
||||
resolve({ type: lang['Control Trigger Ended'] })
|
||||
stopMoveOnvif({
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
}).then((response) => {
|
||||
moveLock[options.ke + options.id] = false
|
||||
})
|
||||
}
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
console.log(new Error())
|
||||
break;
|
||||
default:
|
||||
try{
|
||||
moveLock[options.ke + options.id] = true
|
||||
startMoveOnvif(options).then((moveResponse) => {
|
||||
if(!moveResponse.ok){
|
||||
s.debugLog('ONVIF Move Error',moveResponse)
|
||||
}
|
||||
resolve(moveResponse)
|
||||
})
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
console.log(new Error())
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
})
|
||||
}
|
||||
async function startMove(options){
|
||||
const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
|
||||
const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
|
||||
if(controlUrlMethod === 'ONVIF'){
|
||||
return await moveOnvifCamera(options,true);
|
||||
}else{
|
||||
return await moveGeneric(options,true);
|
||||
}
|
||||
}
|
||||
const ptzControl = async function(options,callback){
|
||||
async function stopMove(options){
|
||||
const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
|
||||
const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
|
||||
if(controlUrlMethod === 'ONVIF'){
|
||||
return await moveOnvifCamera(options,false);
|
||||
}else{
|
||||
return await moveGeneric(options,false);
|
||||
}
|
||||
}
|
||||
async function ptzControl(options,callback){
|
||||
if(!s.group[options.ke] || !s.group[options.ke].activeMonitors[options.id]){return}
|
||||
const monitorConfig = s.group[options.ke].rawMonitorConfigurations[options.id]
|
||||
const controlUrlMethod = monitorConfig.details.control_url_method || 'GET'
|
||||
const controlBaseUrl = monitorConfig.details.control_base_url || s.buildMonitorUrl(monitorConfig, true)
|
||||
const controlUrlStopTimeout = options.moveTimeout || parseInt(monitorConfig.details.control_url_stop_timeout) || 1000
|
||||
const stopCommandEnabled = monitorConfig.details.control_stop === '1' || monitorConfig.details.control_stop === '2';
|
||||
if(monitorConfig.details.control !== "1"){
|
||||
s.userLog(monitorConfig,{type:lang['Control Error'],msg:lang.ControlErrorText1});
|
||||
return
|
||||
s.userLog(monitorConfig,{
|
||||
type: lang['Control Error'],
|
||||
msg: lang.ControlErrorText1
|
||||
});
|
||||
return {
|
||||
ok: false,
|
||||
msg: lang.ControlErrorText1
|
||||
}
|
||||
}
|
||||
if(monitorConfig.details.control_url_stop_timeout === '0' && monitorConfig.details.control_stop === '1' && s.group[options.ke].activeMonitors[options.id].ptzMoving === true){
|
||||
options.direction = 'stopMove'
|
||||
s.group[options.ke].activeMonitors[options.id].ptzMoving = false
|
||||
}else{
|
||||
s.group[options.ke].activeMonitors[options.id].ptzMoving = true
|
||||
let response = {
|
||||
direction: options.direction,
|
||||
}
|
||||
if(controlUrlMethod === 'ONVIF'){
|
||||
try{
|
||||
//create onvif connection
|
||||
if(
|
||||
!s.group[options.ke].activeMonitors[options.id].onvifConnection ||
|
||||
!s.group[options.ke].activeMonitors[options.id].onvifConnection.current_profile ||
|
||||
!s.group[options.ke].activeMonitors[options.id].onvifConnection.current_profile.token
|
||||
){
|
||||
const response = await s.createOnvifDevice({
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
})
|
||||
if(response.ok){
|
||||
moveOnvifCamera({
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
direction: options.direction,
|
||||
axis: options.axis,
|
||||
},(msg) => {
|
||||
msg.msg = options.direction
|
||||
callback(msg)
|
||||
})
|
||||
}else{
|
||||
s.userLog(options,{type:lang['Control Error'],msg:response.error})
|
||||
}
|
||||
if(options.direction === 'center'){
|
||||
response.moveResponse = await moveOnvifCamera(options,true)
|
||||
}else if(stopCommandEnabled){
|
||||
response.moveResponse = await moveOnvifCamera(options,true)
|
||||
if(options.direction !== 'stopMove' && options.direction !== 'center'){
|
||||
await asyncSetTimeout(controlUrlStopTimeout)
|
||||
response.stopMoveResponse = await moveOnvifCamera(options,false)
|
||||
response.ok = response.moveResponse.ok && response.stopMoveResponse.ok;
|
||||
}else{
|
||||
moveOnvifCamera({
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
direction: options.direction,
|
||||
axis: options.axis,
|
||||
},(msg) => {
|
||||
if(!msg.msg)msg.msg = {direction: options.direction}
|
||||
callback(msg)
|
||||
})
|
||||
response.ok = response.moveResponse.ok;
|
||||
}
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
callback({
|
||||
type: lang['Control Error'],
|
||||
msg: {
|
||||
msg: lang.ControlErrorText2,
|
||||
error: err,
|
||||
direction: options.direction
|
||||
}
|
||||
})
|
||||
}else{
|
||||
response = await relativeMoveOnvif(options);
|
||||
}
|
||||
}else{
|
||||
const controlUrlStopTimeout = parseInt(monitorConfig.details.control_url_stop_timeout) || 1000
|
||||
var stopCamera = function(){
|
||||
let stopURL = controlBaseUrl + monitorConfig.details[`control_url_${options.direction}_stop`]
|
||||
let controlOptions = s.cameraControlOptionsFromUrl(stopURL,monitorConfig)
|
||||
const hasDigestAuthEnabled = monitorConfig.details.control_digest_auth === '1'
|
||||
const requestUrl = controlBaseUrl + controlOptions.path
|
||||
const response = {
|
||||
ok: true,
|
||||
type:'Control Trigger Ended'
|
||||
}
|
||||
const theRequest = fetchWithAuthentication(requestUrl,{
|
||||
method: controlOptions.method,
|
||||
digestAuth: hasDigestAuthEnabled,
|
||||
body: controlOptions.postData || null
|
||||
});
|
||||
theRequest.then(res => res.text())
|
||||
.then((data) => {
|
||||
moveLock[options.ke + options.id] = false
|
||||
s.userLog(monitorConfig,response);
|
||||
});
|
||||
theRequest.catch((err) => {
|
||||
response.ok = false
|
||||
response.type = 'Control Error'
|
||||
response.msg = err
|
||||
callback(response)
|
||||
})
|
||||
}
|
||||
if(options.direction === 'stopMove'){
|
||||
stopCamera()
|
||||
response = await moveGeneric(options,false)
|
||||
}else{
|
||||
moveLock[options.ke + options.id] = true
|
||||
let controlURL = controlBaseUrl + monitorConfig.details[`control_url_${options.direction}`]
|
||||
let controlOptions = s.cameraControlOptionsFromUrl(controlURL,monitorConfig)
|
||||
const hasDigestAuthEnabled = monitorConfig.details.control_digest_auth === '1'
|
||||
const requestUrl = controlBaseUrl + controlOptions.path
|
||||
const response = {
|
||||
ok: true,
|
||||
type: lang['Control Triggered']
|
||||
// left, right, up, down, center
|
||||
response.moveResponse = await moveGeneric(options,true)
|
||||
if(stopCommandEnabled){
|
||||
await asyncSetTimeout(controlUrlStopTimeout)
|
||||
response.stopMoveResponse = await moveGeneric(options,false)
|
||||
response.ok = response.moveResponse.ok && response.stopMoveResponse.ok;
|
||||
}else{
|
||||
response.ok = response.moveResponse.ok;
|
||||
}
|
||||
const theRequest = fetchWithAuthentication(requestUrl,{
|
||||
method: controlOptions.method,
|
||||
digestAuth: hasDigestAuthEnabled,
|
||||
body: controlOptions.postData || null
|
||||
});
|
||||
theRequest.then(res => res.text())
|
||||
.then((data) => {
|
||||
if(monitorConfig.details.control_stop == '1' && options.direction !== 'center' ){
|
||||
s.userLog(monitorConfig,{type: lang['Control Trigger Started']});
|
||||
if(controlUrlStopTimeout > 0){
|
||||
setTimeout(function(){
|
||||
stopCamera()
|
||||
},controlUrlStopTimeout)
|
||||
}
|
||||
}else{
|
||||
moveLock[options.ke + options.id] = false
|
||||
callback(response)
|
||||
}
|
||||
});
|
||||
theRequest.catch((err) => {
|
||||
response.ok = false
|
||||
response.type = lang['Control Error']
|
||||
response.msg = err
|
||||
callback(response)
|
||||
})
|
||||
}
|
||||
}
|
||||
if(callback)callback(response);
|
||||
return response;
|
||||
}
|
||||
const getPresetPositions = (options,callback) => {
|
||||
const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
|
||||
|
@ -299,7 +312,6 @@ module.exports = function(s,config,lang){
|
|||
const setPresetForCurrentPosition = (options,callback) => {
|
||||
const nonStandardOnvif = s.group[options.ke].rawMonitorConfigurations[options.id].details.onvif_non_standard === '1'
|
||||
const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
|
||||
console.log(options.PresetToken)
|
||||
s.runOnvifMethod({
|
||||
auth: {
|
||||
ke: options.ke,
|
||||
|
@ -366,7 +378,6 @@ module.exports = function(s,config,lang){
|
|||
const imageCenterY = imgHeight / 2
|
||||
const matrices = event.details.matrices || []
|
||||
const largestMatrix = getLargestMatrix(matrices.filter(matrix => trackingTarget.indexOf(matrix.tag) > -1))
|
||||
// console.log(matrices.find(matrix => matrix.tag === 'person'))
|
||||
if(!largestMatrix)return;
|
||||
const monitorConfig = s.group[event.ke].rawMonitorConfigurations[event.id]
|
||||
const invertedVerticalAxis = monitorConfig.details.control_invert_y === '1'
|
||||
|
@ -375,6 +386,10 @@ module.exports = function(s,config,lang){
|
|||
const matrixCenterY = largestMatrix.y + (largestMatrix.height / 2)
|
||||
const rawDistanceX = (matrixCenterX - imageCenterX)
|
||||
const rawDistanceY = (matrixCenterY - imageCenterY)
|
||||
const percentX = parseFloat((rawDistanceX / imgWidth).toFixed(2));
|
||||
const percentY = parseFloat((rawDistanceY / imgHeight).toFixed(2));
|
||||
const turnSpeedX = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
|
||||
const turnSpeedY = parseFloat(monitorConfig.details.control_turn_speed) || 0.1
|
||||
const distanceX = imgWidth / rawDistanceX
|
||||
const distanceY = imgHeight / rawDistanceY
|
||||
const axisX = rawDistanceX > thresholdX || rawDistanceX < -thresholdX ? distanceX : 0
|
||||
|
@ -382,11 +397,11 @@ module.exports = function(s,config,lang){
|
|||
if(axisX !== 0 || axisY !== 0){
|
||||
ptzControl({
|
||||
axis: [
|
||||
{direction: 'x', amount: axisX === 0 ? 0 : axisX > 0 ? turnSpeed : -turnSpeed},
|
||||
{direction: 'y', amount: axisY === 0 ? 0 : axisY > 0 ? invertedVerticalAxis ? -turnSpeed : turnSpeed : invertedVerticalAxis ? turnSpeed : -turnSpeed},
|
||||
{direction: 'x', amount: axisX === 0 ? 0 : axisX > 0 ? turnSpeedX : -turnSpeedX},
|
||||
{direction: 'y', amount: axisY === 0 ? 0 : axisY > 0 ? invertedVerticalAxis ? -turnSpeedY : turnSpeedY : invertedVerticalAxis ? turnSpeedY : -turnSpeedY},
|
||||
{direction: 'z', amount: 0},
|
||||
],
|
||||
// axis: [{direction: 'x', amount: 1.0}],
|
||||
moveTimeout: 500,
|
||||
id: event.id,
|
||||
ke: event.ke
|
||||
},(msg) => {
|
||||
|
@ -399,12 +414,12 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
}
|
||||
return {
|
||||
ptzControl: ptzControl,
|
||||
startMove: startMove,
|
||||
stopMove: stopMove,
|
||||
getPresetPositions: getPresetPositions,
|
||||
setPresetForCurrentPosition: setPresetForCurrentPosition,
|
||||
moveToPresetPosition: moveToPresetPosition,
|
||||
moveCameraPtzToMatrix: moveCameraPtzToMatrix
|
||||
startMove,
|
||||
stopMove,
|
||||
ptzControl,
|
||||
getPresetPositions,
|
||||
setPresetForCurrentPosition,
|
||||
moveToPresetPosition,
|
||||
moveCameraPtzToMatrix,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,7 +40,6 @@ module.exports = async (s,config,lang,app,io) => {
|
|||
const arguments = s.getPostData(req) || []
|
||||
try{
|
||||
const groupKey = req.params.ke
|
||||
console.log(Object.keys(user))
|
||||
endData.response = await s.group[groupKey].zwave[actionFunction](...arguments)
|
||||
}catch(err){
|
||||
endData.ok = false
|
||||
|
|
|
@ -87,11 +87,11 @@ module.exports = function(s,config){
|
|||
const knexError = (dbQuery,options,err) => {
|
||||
console.error('knexError----------------------------------- START')
|
||||
if(config.debugLogVerbose && config.debugLog === true){
|
||||
s.debugLog('s.knexQuery QUERY',JSON.stringify(options,null,3))
|
||||
s.debugLog('STACK TRACE, NOT AN ',new Error())
|
||||
}
|
||||
console.error(err)
|
||||
console.error(dbQuery.toString())
|
||||
// CANNOT USE `dbQuery.toString()` because it breaks the query
|
||||
console.error(options)
|
||||
console.error('knexError----------------------------------- END')
|
||||
}
|
||||
const knexQuery = (options,callback) => {
|
||||
|
@ -149,7 +149,8 @@ module.exports = function(s,config){
|
|||
}
|
||||
}
|
||||
if(config.debugLog === true){
|
||||
console.log(dbQuery.toString())
|
||||
// CANNOT USE `dbQuery.toString()` because it breaks the query
|
||||
console.log(options)
|
||||
}
|
||||
if(callback || options.update || options.insert || options.action === 'delete'){
|
||||
dbQuery.asCallback(function(err,r) {
|
||||
|
|
|
@ -87,12 +87,13 @@ module.exports = function(s,config,lang){
|
|||
s.onMonitorStart((monitorConfig) => {
|
||||
initializeOnvifEvents(monitorConfig)
|
||||
})
|
||||
const connectionInfoArray = s.definitions["Monitor Settings"].blocks["Connection"].info
|
||||
connectionInfoArray.splice(connectionInfoArray.length - 2, 0, {
|
||||
const connectionInfoArray = s.definitions["Monitor Settings"].blocks["Detector"].info
|
||||
connectionInfoArray.splice(2, 0, {
|
||||
"name": "detail=onvif_events",
|
||||
"field": lang['ONVIF Events'],
|
||||
"default": "0",
|
||||
"form-group-class": "h_onvif_input h_onvif_1",
|
||||
"form-group-class-pre-layer": "h_det_input h_det_1",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
|
|
|
@ -152,7 +152,6 @@ module.exports = (s,config,lang,app,io) => {
|
|||
}
|
||||
const addToEventCounter = (eventData) => {
|
||||
const eventsCounted = s.group[eventData.ke].activeMonitors[eventData.id].detector_motion_count
|
||||
s.debugLog(`addToEventCounter`,eventData,eventsCounted.length)
|
||||
eventsCounted.push(eventData)
|
||||
}
|
||||
const clearEventCounter = (groupKey,monitorId) => {
|
||||
|
@ -420,7 +419,8 @@ module.exports = (s,config,lang,app,io) => {
|
|||
monitorConfig.mode === 'start' &&
|
||||
(monitorDetails.detector_record_method === 'sip' || monitorDetails.detector_record_method === 'hot')
|
||||
){
|
||||
createEventBasedRecording(d,moment(eventTime).subtract(5,'seconds').format('YYYY-MM-DDTHH-mm-ss'))
|
||||
const secondBefore = (parseInt(monitorDetails.detector_buffer_seconds_before) || 5) + 1
|
||||
createEventBasedRecording(d,moment(eventTime).subtract(secondBefore,'seconds').format('YYYY-MM-DDTHH-mm-ss'))
|
||||
}
|
||||
d.currentTime = eventTime
|
||||
d.currentTimestamp = s.timeObject(d.currentTime).format()
|
||||
|
@ -527,14 +527,26 @@ module.exports = (s,config,lang,app,io) => {
|
|||
var ffmpegError = ''
|
||||
var error
|
||||
var filename = fileTime + '.mp4'
|
||||
let outputMap = `-map 0:0 `
|
||||
const analyzeDuration = parseInt(monitorDetails.event_record_aduration) || 1000
|
||||
const probeSize = parseInt(monitorDetails.event_record_probesize) || 32
|
||||
s.userLog(d,{
|
||||
type: logTitleText,
|
||||
msg: lang["Started"]
|
||||
})
|
||||
//-t 00:'+s.timeObject(new Date(detector_timeout * 1000 * 60)).format('mm:ss')+'
|
||||
if(
|
||||
monitorDetails.detector_buffer_acodec &&
|
||||
monitorDetails.detector_buffer_acodec !== 'no' &&
|
||||
monitorDetails.detector_buffer_acodec !== 'auto'
|
||||
){
|
||||
outputMap += `-map 0:1 `
|
||||
}
|
||||
const ffmpegCommand = `-loglevel warning -live_start_index -99999 -analyzeduration ${analyzeDuration} -probesize ${probeSize} -re -i "${s.dir.streams+d.ke+'/'+d.id}/detectorStream.m3u8" ${outputMap}-movflags faststart+frag_keyframe+empty_moov -fflags +igndts -c:v copy -c:a aac -strict -2 -strftime 1 -y "${s.getVideoDirectory(monitorConfig) + filename}"`
|
||||
s.debugLog(ffmpegCommand)
|
||||
activeMonitor.eventBasedRecording.process = spawn(
|
||||
config.ffmpegDir,
|
||||
splitForFFPMEG(('-loglevel warning -analyzeduration 1000000 -probesize 1000000 -re -i "'+s.dir.streams+d.ke+'/'+d.id+'/detectorStream.m3u8" -movflags faststart+frag_keyframe+empty_moov -fflags +igndts -c:v copy -strftime 1 "'+s.getVideoDirectory(monitorConfig) + filename + '"'))
|
||||
splitForFFPMEG(ffmpegCommand)
|
||||
)
|
||||
activeMonitor.eventBasedRecording.process.stderr.on('data',function(data){
|
||||
s.userLog(d,{
|
||||
|
|
|
@ -1,211 +1,66 @@
|
|||
module.exports = function(s,config){
|
||||
////// USER //////
|
||||
s.onSocketAuthenticationExtensions = []
|
||||
s.onSocketAuthentication = function(callback){
|
||||
s.onSocketAuthenticationExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onUserLogExtensions = []
|
||||
s.onUserLog = function(callback){
|
||||
s.onUserLogExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.loadGroupExtensions = []
|
||||
s.loadGroupExtender = function(callback){
|
||||
s.loadGroupExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.loadGroupAppExtensions = []
|
||||
s.loadGroupAppExtender = function(callback){
|
||||
s.loadGroupAppExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.unloadGroupAppExtensions = []
|
||||
s.unloadGroupAppExtender = function(callback){
|
||||
s.unloadGroupAppExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.cloudDisksLoaded = []
|
||||
s.cloudDisksLoader = function(storageType){
|
||||
s.cloudDisksLoaded.push(storageType)
|
||||
}
|
||||
//
|
||||
s.onAccountSaveExtensions = []
|
||||
s.onAccountSave = function(callback){
|
||||
s.onAccountSaveExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.beforeAccountSaveExtensions = []
|
||||
s.beforeAccountSave = function(callback){
|
||||
s.beforeAccountSaveExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onTwoFactorAuthCodeNotificationExtensions = []
|
||||
s.onTwoFactorAuthCodeNotification = function(callback){
|
||||
s.onTwoFactorAuthCodeNotificationExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onStalePurgeLockExtensions = []
|
||||
s.onStalePurgeLock = function(callback){
|
||||
s.onStalePurgeLockExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.cloudDiskUseStartupExtensions = {}
|
||||
s.cloudDiskUseOnGetVideoDataExtensions = {}
|
||||
|
||||
function createExtension(nameOfExtension,nameOfExtensionContainer,objective){
|
||||
nameOfExtensionContainer = nameOfExtensionContainer || `${nameOfExtension}Extensions`
|
||||
if(objective){
|
||||
s[nameOfExtensionContainer] = []
|
||||
s[nameOfExtension] = function(nameOfCallback,callback){
|
||||
s[nameOfExtensionContainer][nameOfCallback] = callback
|
||||
}
|
||||
}else{
|
||||
s[nameOfExtensionContainer] = []
|
||||
s[nameOfExtension] = function(callback){
|
||||
s[nameOfExtensionContainer].push(callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
////// USER //////
|
||||
createExtension(`onSocketAuthentication`)
|
||||
createExtension(`onUserLog`)
|
||||
createExtension(`loadGroupExtender`,`loadGroupExtensions`)
|
||||
createExtension(`loadGroupAppExtender`,`loadGroupAppExtensions`)
|
||||
createExtension(`unloadGroupAppExtender`,`unloadGroupAppExtensions`)
|
||||
createExtension(`cloudDisksLoader`,`cloudDisksLoaded`)
|
||||
createExtension(`onAccountSave`)
|
||||
createExtension(`beforeAccountSave`)
|
||||
createExtension(`onTwoFactorAuthCodeNotification`)
|
||||
createExtension(`onStalePurgeLock`)
|
||||
////// EVENTS //////
|
||||
s.onEventTriggerExtensions = []
|
||||
s.onEventTrigger = function(callback){
|
||||
s.onEventTriggerExtensions.push(callback)
|
||||
}
|
||||
s.onEventTriggerBeforeFilterExtensions = []
|
||||
s.onEventTriggerBeforeFilter = function(callback){
|
||||
s.onEventTriggerBeforeFilterExtensions.push(callback)
|
||||
}
|
||||
s.onFilterEventExtensions = []
|
||||
s.onFilterEvent = function(callback){
|
||||
s.onFilterEventExtensions.push(callback)
|
||||
}
|
||||
|
||||
createExtension(`onEventTrigger`)
|
||||
createExtension(`onEventTriggerBeforeFilter`)
|
||||
createExtension(`onFilterEvent`)
|
||||
////// MONITOR //////
|
||||
s.onMonitorInitExtensions = []
|
||||
s.onMonitorInit = function(callback){
|
||||
s.onMonitorInitExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorStartExtensions = []
|
||||
s.onMonitorStart = function(callback){
|
||||
s.onMonitorStartExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorStopExtensions = []
|
||||
s.onMonitorStop = function(callback){
|
||||
s.onMonitorStopExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorSaveExtensions = []
|
||||
s.onMonitorSave = function(callback){
|
||||
s.onMonitorSaveExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorUnexpectedExitExtensions = []
|
||||
s.onMonitorUnexpectedExit = function(callback){
|
||||
s.onMonitorUnexpectedExitExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onDetectorNoTriggerTimeoutExtensions = []
|
||||
s.onDetectorNoTriggerTimeout = function(callback){
|
||||
s.onDetectorNoTriggerTimeoutExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onFfmpegCameraStringCreationExtensions = []
|
||||
s.onFfmpegCameraStringCreation = function(callback){
|
||||
s.onFfmpegCameraStringCreationExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onFfmpegBuildMainStreamExtensions = []
|
||||
s.onFfmpegBuildMainStream = function(callback){
|
||||
s.onFfmpegBuildMainStreamExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onFfmpegBuildStreamChannelExtensions = []
|
||||
s.onFfmpegBuildStreamChannel = function(callback){
|
||||
s.onFfmpegBuildStreamChannelExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorPingFailedExtensions = []
|
||||
s.onMonitorPingFailed = function(callback){
|
||||
s.onMonitorPingFailedExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorDiedExtensions = []
|
||||
s.onMonitorDied = function(callback){
|
||||
s.onMonitorDiedExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onMonitorCreateStreamPipeExtensions = []
|
||||
s.onMonitorCreateStreamPipe = function(callback){
|
||||
s.onMonitorCreateStreamPipeExtensions.push(callback)
|
||||
}
|
||||
|
||||
createExtension(`onMonitorInit`)
|
||||
createExtension(`onMonitorStart`)
|
||||
createExtension(`onMonitorStop`)
|
||||
createExtension(`onMonitorSave`)
|
||||
createExtension(`onMonitorUnexpectedExit`)
|
||||
createExtension(`onDetectorNoTriggerTimeout`)
|
||||
createExtension(`onFfmpegCameraStringCreation`)
|
||||
createExtension(`onFfmpegBuildMainStream`)
|
||||
createExtension(`onFfmpegBuildStreamChannel`)
|
||||
createExtension(`onMonitorPingFailed`)
|
||||
createExtension(`onMonitorDied`)
|
||||
createExtension(`onMonitorCreateStreamPipe`)
|
||||
///////// SYSTEM ////////
|
||||
s.onProcessReadyExtensions = []
|
||||
s.onProcessReady = function(callback){
|
||||
s.onProcessReadyExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onProcessExitExtensions = []
|
||||
s.onProcessExit = function(callback){
|
||||
s.onProcessExitExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onBeforeDatabaseLoadExtensions = []
|
||||
s.onBeforeDatabaseLoad = function(callback){
|
||||
s.onBeforeDatabaseLoadExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onFFmpegLoadedExtensions = []
|
||||
s.onFFmpegLoaded = function(callback){
|
||||
s.onFFmpegLoadedExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.beforeMonitorsLoadedOnStartupExtensions = []
|
||||
s.beforeMonitorsLoadedOnStartup = function(callback){
|
||||
s.beforeMonitorsLoadedOnStartupExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onWebSocketConnectionExtensions = []
|
||||
s.onWebSocketConnection = function(callback){
|
||||
s.onWebSocketConnectionExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onWebSocketDisconnectionExtensions = []
|
||||
s.onWebSocketDisconnection = function(callback){
|
||||
s.onWebSocketDisconnectionExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onWebsocketMessageSendExtensions = []
|
||||
s.onWebsocketMessageSend = function(callback){
|
||||
s.onWebsocketMessageSendExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onGetCpuUsageExtensions = []
|
||||
s.onGetCpuUsage = function(callback){
|
||||
s.onGetCpuUsageExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onGetRamUsageExtensions = []
|
||||
s.onGetRamUsage = function(callback){
|
||||
s.onGetRamUsageExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onSubscriptionCheckExtensions = []
|
||||
s.onSubscriptionCheck = function(callback){
|
||||
s.onSubscriptionCheckExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onDataPortMessageExtensions = []
|
||||
s.onDataPortMessage = function(callback){
|
||||
s.onDataPortMessageExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
s.onHttpRequestUpgradeExtensions = {}
|
||||
s.onHttpRequestUpgrade = function(nameOfCallback,callback){
|
||||
s.onHttpRequestUpgradeExtensions[nameOfCallback] = callback
|
||||
}
|
||||
//
|
||||
createExtension(`onProcessReady`)
|
||||
createExtension(`onProcessExit`)
|
||||
createExtension(`onBeforeDatabaseLoad`)
|
||||
createExtension(`onFFmpegLoaded`)
|
||||
createExtension(`beforeMonitorsLoadedOnStartup`)
|
||||
createExtension(`onWebSocketConnection`)
|
||||
createExtension(`onWebSocketDisconnection`)
|
||||
createExtension(`onWebsocketMessageSend`)
|
||||
createExtension(`onOtherWebSocketMessages`)
|
||||
createExtension(`onGetCpuUsage`)
|
||||
createExtension(`onGetRamUsage`)
|
||||
createExtension(`onSubscriptionCheck`)
|
||||
createExtension(`onDataPortMessage`)
|
||||
createExtension(`onHttpRequestUpgrade`,null,true)
|
||||
/////// VIDEOS ////////
|
||||
s.insertCompletedVideoExtensions = []
|
||||
s.insertCompletedVideoExtender = function(callback){
|
||||
s.insertCompletedVideoExtensions.push(callback)
|
||||
}
|
||||
s.onBeforeInsertCompletedVideoExtensions = []
|
||||
s.onBeforeInsertCompletedVideo = function(callback){
|
||||
s.onBeforeInsertCompletedVideoExtensions.push(callback)
|
||||
}
|
||||
createExtension(`insertCompletedVideoExtender`,`insertCompletedVideoExtensions`)
|
||||
createExtension(`onBeforeInsertCompletedVideo`)
|
||||
/////// TIMELAPSE ////////
|
||||
s.onInsertTimelapseFrameExtensions = []
|
||||
s.onInsertTimelapseFrame = function(callback){
|
||||
s.onInsertTimelapseFrameExtensions.push(callback)
|
||||
}
|
||||
createExtension(`onInsertTimelapseFrame`)
|
||||
}
|
||||
|
|
|
@ -679,13 +679,17 @@ module.exports = (s,config,lang) => {
|
|||
const isCudaEnabled = hasCudaEnabled(e)
|
||||
const outputFilters = []
|
||||
var videoCodec = e.details.detector_buffer_vcodec
|
||||
var liveStartIndex = e.details.detector_buffer_live_start_index || '-3'
|
||||
var audioCodec = e.details.detector_buffer_acodec ? e.details.detector_buffer_acodec : 'no'
|
||||
const videoCodecisCopy = videoCodec === 'copy'
|
||||
const videoFps = !isNaN(parseFloat(e.details.stream_fps)) && e.details.stream_fps !== '0' ? parseFloat(e.details.stream_fps) : null
|
||||
const inputMap = buildInputMap(e,e.details.input_map_choices.detector_sip_buffer)
|
||||
const { videoWidth, videoHeight } = validateDimensions(e.details.event_record_scale_x,e.details.event_record_scale_y)
|
||||
const hlsTime = !isNaN(parseInt(e.details.detector_buffer_hls_time)) ? `${parseInt(e.details.detector_buffer_hls_time)}` : '2'
|
||||
const hlsListSize = !isNaN(parseInt(e.details.detector_buffer_hls_list_size)) ? `${parseInt(e.details.detector_buffer_hls_list_size)}` : '4'
|
||||
// const hlsListSize = !isNaN(parseInt(e.details.detector_buffer_hls_list_size)) ? `${parseInt(e.details.detector_buffer_hls_list_size)}` : '4'
|
||||
const secondsBefore = parseInt(e.details.detector_buffer_seconds_before) || 5
|
||||
let hlsListSize = parseInt(secondsBefore * 0.6)
|
||||
hlsListSize = hlsListSize < 3 ? 3 : hlsListSize;
|
||||
if(inputMap)outputFlags.push(inputMap)
|
||||
if(e.details.cust_sip_record)outputFlags.push(e.details.cust_sip_record)
|
||||
if(videoCodec === 'auto'){
|
||||
|
|
|
@ -130,7 +130,11 @@ module.exports = (s,config,lang) => {
|
|||
}else{
|
||||
const details = s.parseJSON(configPartial.details)
|
||||
Object.keys(details).forEach((key) => {
|
||||
activeMonitor.details[key] = details[key]
|
||||
try{
|
||||
activeMonitor.details[key] = details[key]
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -215,12 +215,20 @@ module.exports = function(s,config,lang,app,io){
|
|||
}
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,monitorId)
|
||||
if(user.details.sub && user.details.allmonitors === '0' && (user.permissions.get_monitors === "0" || monitorRestrictions.length === 0)){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Permitted']
|
||||
})
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_video_view`]
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
s.knexQuery({
|
||||
|
|
|
@ -55,6 +55,9 @@ module.exports = function(s,config,lang,io){
|
|||
exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
|
||||
if(s.isWin===true){
|
||||
d=d.replace(/(\r\n|\n|\r)/gm,"").replace(/%/g,"")
|
||||
}else if(s.platform == 'darwin') {
|
||||
// on macos the cpu% is per core, so a 10 core machine can go up to 1000%
|
||||
d=parseFloat(d.trim()) / s.coreCount
|
||||
}
|
||||
resolve(d)
|
||||
s.onGetCpuUsageExtensions.forEach(function(extender){
|
||||
|
@ -100,18 +103,31 @@ module.exports = function(s,config,lang,io){
|
|||
k.cmd = "LANG=C free | grep Mem | awk '{print $7/$2 * 100.0}'";
|
||||
break;
|
||||
}
|
||||
let percent = 0
|
||||
let used = 0
|
||||
if(k.cmd){
|
||||
exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
|
||||
if(s.isWin===true){
|
||||
d=(parseInt(d.split('=')[1])/(s.totalmem/1000))*100
|
||||
}
|
||||
resolve(d)
|
||||
s.onGetRamUsageExtensions.forEach(function(extender){
|
||||
extender(d)
|
||||
})
|
||||
})
|
||||
exec(k.cmd,{encoding:'utf8',detached: true},function(err,d){
|
||||
if(s.isWin===true){
|
||||
const freeMb = parseInt(d.split('=')[1].trim()) / 1024
|
||||
const totalMemInMb = s.totalmem/1024/1024
|
||||
used = totalMemInMb - freeMb
|
||||
percent=(used/totalMemInMb)*100
|
||||
} else {
|
||||
percent = d.trim()
|
||||
}
|
||||
resolve({
|
||||
used,
|
||||
percent
|
||||
})
|
||||
s.onGetRamUsageExtensions.forEach(function(extender){
|
||||
extender(percent)
|
||||
})
|
||||
})
|
||||
}else{
|
||||
resolve(0)
|
||||
resolve({
|
||||
used,
|
||||
percent
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
117
libs/monitor.js
117
libs/monitor.js
|
@ -1773,6 +1773,7 @@ module.exports = function(s,config,lang){
|
|||
monitorRestrictions.push(['or','mid','=',v])
|
||||
}
|
||||
})
|
||||
console.log(monitorRestrictions)
|
||||
}catch(er){
|
||||
}
|
||||
}else if(
|
||||
|
@ -1790,20 +1791,104 @@ module.exports = function(s,config,lang){
|
|||
){}
|
||||
return monitorRestrictions
|
||||
}
|
||||
// s.checkViewerConnectionsForMonitor = function(monitorObject){
|
||||
// var monitorConfig = s.group[monitorObject.ke].rawMonitorConfigurations[monitorObject.mid]
|
||||
// if(monitorConfig.mode === 'start'){
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// s.addViewerConnectionForMonitor = function(monitorObject,viewerDetails){
|
||||
// s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnection[viewerDetails.viewerId] = viewerDetails
|
||||
// s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount += 1
|
||||
// return s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount
|
||||
// }
|
||||
// s.removeViewerConnectionForMonitor = function(monitorObject,viewerDetails){
|
||||
// delete(s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnection[viewerDetails.viewerId])
|
||||
// s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount -= 1
|
||||
// return s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount
|
||||
// }
|
||||
s.checkPermission = (user) => {
|
||||
// provide "user" object given from "s.auth"
|
||||
const isSubAccount = !!user.details.sub
|
||||
const response = {
|
||||
isSubAccount,
|
||||
hasAllPermissions: isSubAccount && user.details.allmonitors === '1',
|
||||
isRestricted: isSubAccount && user.details.allmonitors !== '1',
|
||||
isRestrictedApiKey: false,
|
||||
apiKeyPermissions: {},
|
||||
userPermissions: {},
|
||||
}
|
||||
const permissions = user.permissions
|
||||
const details = user.details;
|
||||
[
|
||||
'auth_socket',
|
||||
'get_monitors',
|
||||
'control_monitors',
|
||||
'get_logs',
|
||||
'watch_stream',
|
||||
'watch_snapshot',
|
||||
'watch_videos',
|
||||
'delete_videos',
|
||||
].forEach((key) => {
|
||||
const permissionOff = permissions[key] === '0';
|
||||
response.apiKeyPermissions[key] = permissions[key] === '1';
|
||||
response.apiKeyPermissions[`${key}_disallowed`] = permissionOff;
|
||||
response.isRestrictedApiKey = response.isRestrictedApiKey || permissionOff;
|
||||
});
|
||||
// Base Level Permissions
|
||||
// allmonitors : All Monitors and Privileges
|
||||
// monitor_create : Can Create and Delete Monitors
|
||||
// user_change : Can Change User Settings
|
||||
// view_logs : Can View Logs
|
||||
[
|
||||
'allmonitors',
|
||||
'monitor_create',
|
||||
'user_change',
|
||||
'view_logs',
|
||||
].forEach((key) => {
|
||||
response.userPermissions[key] = details[key] === '1' || !details[key];
|
||||
response.userPermissions[`${key}_disallowed`] = details[key] === '0';
|
||||
});
|
||||
return response
|
||||
}
|
||||
s.getMonitorsPermitted = (userDetails,monitorId) => {
|
||||
const monitorRestrictions = []
|
||||
const monitors = {}
|
||||
function setMonitorPermissions(mid){
|
||||
// monitors : Can View Monitor
|
||||
// monitor_edit : Can Edit Monitor (Delete as well)
|
||||
// video_view : Can View Videos and Events
|
||||
// video_delete : Can Delete Videos and Events
|
||||
[
|
||||
'monitors',
|
||||
'monitor_edit',
|
||||
'video_view',
|
||||
'video_delete',
|
||||
].forEach((key) => {
|
||||
monitors[`${mid}_${key}`] = userDetails[key] && userDetails[key].indexOf(mid) > -1 || false;
|
||||
});
|
||||
return true
|
||||
}
|
||||
function addToQuery(mid,n){
|
||||
if(n === 0){
|
||||
monitorRestrictions.push(['mid','=',mid])
|
||||
}else{
|
||||
monitorRestrictions.push(['or','mid','=',mid])
|
||||
}
|
||||
};
|
||||
if(
|
||||
!monitorId &&
|
||||
userDetails.sub &&
|
||||
userDetails.monitors &&
|
||||
userDetails.allmonitors !== '1'
|
||||
){
|
||||
try{
|
||||
userDetails.monitors = s.parseJSON(userDetails.monitors)
|
||||
userDetails.monitors.forEach(function(v,n){
|
||||
setMonitorPermissions(v)
|
||||
addToQuery(v,n)
|
||||
})
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
}else if(
|
||||
monitorId && (
|
||||
!userDetails.sub ||
|
||||
userDetails.allmonitors !== '0' ||
|
||||
userDetails.monitors.indexOf(monitorId) >- 1
|
||||
)
|
||||
){
|
||||
setMonitorPermissions(monitorId)
|
||||
addToQuery(monitorId,0)
|
||||
}
|
||||
return {
|
||||
monitorPermissions: monitors,
|
||||
// queryConditions
|
||||
monitorRestrictions: monitorRestrictions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,9 @@ module.exports = (s,config,lang) => {
|
|||
resolve(response)
|
||||
}
|
||||
try{
|
||||
proc.stdin.write("q\r\n")
|
||||
if(proc && proc.stdin) {
|
||||
proc.stdin.write("q\r\n");
|
||||
}
|
||||
setTimeout(() => {
|
||||
if(proc && proc.kill){
|
||||
if(s.isWin){
|
||||
|
@ -473,7 +475,102 @@ module.exports = (s,config,lang) => {
|
|||
// },1000 * 60)
|
||||
// }
|
||||
}
|
||||
async function deleteMonitorData(groupKey,monitorId){
|
||||
// deleteVideos
|
||||
// deleteFileBinFiles
|
||||
// deleteTimelapseFrames
|
||||
async function deletePath(thePath){
|
||||
try{
|
||||
await fs.promises.stat(thePath)
|
||||
await fs.promises.rm(thePath, {recursive: true})
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
}
|
||||
async function deleteFromTable(tableName){
|
||||
await s.knexQueryPromise({
|
||||
action: "delete",
|
||||
table: tableName,
|
||||
where: {
|
||||
ke: groupKey,
|
||||
mid: monitorId,
|
||||
}
|
||||
})
|
||||
}
|
||||
async function getSizeFromTable(tableName){
|
||||
const response = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: "size",
|
||||
table: tableName,
|
||||
where: {
|
||||
ke: groupKey,
|
||||
mid: monitorId,
|
||||
}
|
||||
})
|
||||
const rows = response.rows
|
||||
let size = 0
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i]
|
||||
size += row.size
|
||||
}
|
||||
return size
|
||||
}
|
||||
async function adjustSpaceCounterForTableWithAddStorage(tableName,storageType){
|
||||
// does normal videos and addStorage
|
||||
const response = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: "ke,mid,details,size",
|
||||
table: tableName || 'Videos',
|
||||
where: {
|
||||
ke: groupKey,
|
||||
mid: monitorId,
|
||||
}
|
||||
})
|
||||
const rows = response.rows
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const video = rows[i]
|
||||
const storageIndex = s.getVideoStorageIndex(video)
|
||||
if(storageIndex){
|
||||
s.setDiskUsedForGroupAddStorage(video.ke,{
|
||||
size: -(video.size / 1048576),
|
||||
storageIndex: storageIndex
|
||||
},storageType)
|
||||
}else{
|
||||
s.setDiskUsedForGroup(video.ke,-(video.size / 1048576),storageType)
|
||||
}
|
||||
}
|
||||
}
|
||||
async function adjustSpaceCounter(tableName,storageType){
|
||||
const amount = await getSizeFromTable(tableName)
|
||||
s.setDiskUsedForGroup(groupKey,-amount,storageType)
|
||||
}
|
||||
const videosDir = s.dir.videos + `${groupKey}/${monitorId}`
|
||||
const binDir = s.dir.fileBin + `${groupKey}/${monitorId}`
|
||||
|
||||
// videos and addStorage
|
||||
await adjustSpaceCounterForTableWithAddStorage('Timelapse Frames','timelapeFrames')
|
||||
await adjustSpaceCounterForTableWithAddStorage('Videos')
|
||||
await deleteFromTable('Videos')
|
||||
await deletePath(videosDir)
|
||||
for (let i = 0; i < s.dir.addStorage.length; i++) {
|
||||
const storage = s.dir.addStorage[i]
|
||||
const addStorageDir = storage.path + groupKey + '/' + monitorId
|
||||
await deletePath(addStorageDir)
|
||||
await deletePath(addStorageDir + '_timelapse')
|
||||
}
|
||||
|
||||
// timelapse frames
|
||||
await adjustSpaceCounter('Timelapse Frames','timelapeFrames')
|
||||
await deleteFromTable('Timelapse Frames')
|
||||
await deletePath(videosDir + '_timelapse')
|
||||
|
||||
// fileBin
|
||||
await adjustSpaceCounter('Files','fileBin')
|
||||
await deleteFromTable('Files')
|
||||
await deletePath(binDir)
|
||||
}
|
||||
return {
|
||||
deleteMonitorData,
|
||||
cameraDestroy: cameraDestroy,
|
||||
createSnapshot: createSnapshot,
|
||||
processKill: processKill,
|
||||
|
|
|
@ -66,6 +66,28 @@ module.exports = function(s,config,lang,getSnapshot){
|
|||
clearTimeout(s.group[d.ke].activeMonitors[d.id].detector_discordbot);
|
||||
s.group[d.ke].activeMonitors[d.id].detector_discordbot = null
|
||||
},detector_discordbot_timeout)
|
||||
await getSnapshot(d,monitorConfig)
|
||||
if(d.screenshotBuffer){
|
||||
sendMessage({
|
||||
author: {
|
||||
name: s.group[d.ke].rawMonitorConfigurations[d.id].name,
|
||||
icon_url: config.iconURL
|
||||
},
|
||||
title: lang.Event+' - '+d.screenshotName,
|
||||
description: lang.EventText1+' '+d.currentTimestamp,
|
||||
fields: [],
|
||||
timestamp: d.currentTime,
|
||||
footer: {
|
||||
icon_url: config.iconURL,
|
||||
text: "Shinobi Systems"
|
||||
}
|
||||
},[
|
||||
{
|
||||
attachment: d.screenshotBuffer,
|
||||
name: d.screenshotName+'.jpg'
|
||||
}
|
||||
],d.ke)
|
||||
}
|
||||
if(monitorConfig.details.detector_discordbot_send_video === '1'){
|
||||
let videoPath = null
|
||||
let videoName = null
|
||||
|
@ -102,28 +124,6 @@ module.exports = function(s,config,lang,getSnapshot){
|
|||
],d.ke)
|
||||
}
|
||||
}
|
||||
await getSnapshot(d,monitorConfig)
|
||||
if(d.screenshotBuffer){
|
||||
sendMessage({
|
||||
author: {
|
||||
name: s.group[d.ke].rawMonitorConfigurations[d.id].name,
|
||||
icon_url: config.iconURL
|
||||
},
|
||||
title: lang.Event+' - '+d.screenshotName,
|
||||
description: lang.EventText1+' '+d.currentTimestamp,
|
||||
fields: [],
|
||||
timestamp: d.currentTime,
|
||||
footer: {
|
||||
icon_url: config.iconURL,
|
||||
text: "Shinobi Systems"
|
||||
}
|
||||
},[
|
||||
{
|
||||
attachment: d.screenshotBuffer,
|
||||
name: d.screenshotName+'.jpg'
|
||||
}
|
||||
],d.ke)
|
||||
}
|
||||
}
|
||||
}
|
||||
const onTwoFactorAuthCodeNotificationForDiscord = function(r){
|
||||
|
|
|
@ -149,6 +149,13 @@ module.exports = function(s,config,lang,getSnapshot){
|
|||
}
|
||||
})
|
||||
}
|
||||
await getSnapshot(d,monitorConfig)
|
||||
sendMail([
|
||||
{
|
||||
filename: d.screenshotName + '.jpg',
|
||||
content: d.screenshotBuffer
|
||||
}
|
||||
])
|
||||
if(monitorConfig.details.detector_mail_send_video === '1'){
|
||||
let videoPath = null
|
||||
let videoName = null
|
||||
|
@ -188,13 +195,6 @@ module.exports = function(s,config,lang,getSnapshot){
|
|||
})
|
||||
}
|
||||
}
|
||||
await getSnapshot(d,monitorConfig)
|
||||
sendMail([
|
||||
{
|
||||
filename: d.screenshotName + '.jpg',
|
||||
content: d.screenshotBuffer
|
||||
}
|
||||
])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,6 +150,13 @@ module.exports = function (s, config, lang, getSnapshot) {
|
|||
}),
|
||||
},files || [],d.ke)
|
||||
}
|
||||
await getSnapshot(d,monitorConfig)
|
||||
sendMail([
|
||||
{
|
||||
filename: d.screenshotName + '.jpg',
|
||||
content: d.screenshotBuffer
|
||||
}
|
||||
])
|
||||
if(monitorConfig.details.detector_mail_send_video === '1'){
|
||||
let videoPath = null
|
||||
let videoName = null
|
||||
|
@ -178,13 +185,6 @@ module.exports = function (s, config, lang, getSnapshot) {
|
|||
})
|
||||
}
|
||||
}
|
||||
await getSnapshot(d,monitorConfig)
|
||||
sendMail([
|
||||
{
|
||||
filename: d.screenshotName + '.jpg',
|
||||
content: d.screenshotBuffer
|
||||
}
|
||||
])
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ module.exports = function(s,config,lang,getSnapshot){
|
|||
username: username,
|
||||
password: password,
|
||||
clientId: `shinobi_${Math.random().toString(16).substr(2, 8)}`,
|
||||
reconnectPeriod: 1000, // 10 seconds
|
||||
reconnectPeriod: 10000, // 10 seconds
|
||||
});
|
||||
client.on('reconnect', (e) => mqttUserLog(`MQTT Reconnected`))
|
||||
client.on('disconnect', (e) => mqttUserLog(`MQTT Disconnected`))
|
||||
|
|
|
@ -68,6 +68,19 @@ module.exports = function(s,config,lang,getSnapshot){
|
|||
clearTimeout(s.group[d.ke].activeMonitors[d.id].detector_telegrambot);
|
||||
s.group[d.ke].activeMonitors[d.id].detector_telegrambot = null
|
||||
},detector_telegrambot_timeout)
|
||||
await getSnapshot(d,monitorConfig)
|
||||
if(d.screenshotBuffer){
|
||||
sendMessage({
|
||||
title: lang.Event+' - '+d.screenshotName,
|
||||
description: lang.EventText1+' '+d.currentTimestamp,
|
||||
},[
|
||||
{
|
||||
type: 'photo',
|
||||
attachment: d.screenshotBuffer,
|
||||
name: d.screenshotName+'.jpg'
|
||||
}
|
||||
],d.ke)
|
||||
}
|
||||
if(monitorConfig.details.detector_telegrambot_send_video === '1'){
|
||||
// await asyncSetTimeout(3000)
|
||||
let videoPath = null
|
||||
|
@ -96,19 +109,6 @@ module.exports = function(s,config,lang,getSnapshot){
|
|||
],d.ke)
|
||||
}
|
||||
}
|
||||
await getSnapshot(d,monitorConfig)
|
||||
if(d.screenshotBuffer){
|
||||
sendMessage({
|
||||
title: lang.Event+' - '+d.screenshotName,
|
||||
description: lang.EventText1+' '+d.currentTimestamp,
|
||||
},[
|
||||
{
|
||||
type: 'photo',
|
||||
attachment: d.screenshotBuffer,
|
||||
name: d.screenshotName+'.jpg'
|
||||
}
|
||||
],d.ke)
|
||||
}
|
||||
}
|
||||
}
|
||||
const onTwoFactorAuthCodeNotificationForTelegram = function(r){
|
||||
|
|
|
@ -26,10 +26,26 @@ module.exports = function(s,config,lang,app,io){
|
|||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/onvifDeviceManager/:ke/:id',function (req,res){
|
||||
s.auth(req.params,async (user) => {
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.get_monitors_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_monitors`]
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
const endData = {ok: true}
|
||||
try{
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const onvifDevice = await getOnvifDevice(groupKey,monitorId)
|
||||
const cameraInfo = await getUIFieldValues(onvifDevice)
|
||||
endData.onvifData = cameraInfo
|
||||
|
@ -46,11 +62,29 @@ module.exports = function(s,config,lang,app,io){
|
|||
*/
|
||||
app.post(config.webPaths.apiPrefix+':auth/onvifDeviceManager/:ke/:id/save',function (req,res){
|
||||
s.auth(req.params,async (user) => {
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId);
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Authorized']
|
||||
});
|
||||
return
|
||||
}
|
||||
const endData = {ok: true}
|
||||
const responses = {}
|
||||
try{
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const onvifDevice = await getOnvifDevice(groupKey,monitorId)
|
||||
const form = s.getPostData(req)
|
||||
const videoToken = form.VideoConfiguration && form.VideoConfiguration.videoToken ? form.VideoConfiguration.videoToken : null
|
||||
|
@ -100,10 +134,28 @@ module.exports = function(s,config,lang,app,io){
|
|||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/onvifDeviceManager/:ke/:id/reboot',function (req,res){
|
||||
s.auth(req.params,async (user) => {
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId);
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Authorized']
|
||||
});
|
||||
return
|
||||
}
|
||||
const endData = {ok: true}
|
||||
try{
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const onvifDevice = await getOnvifDevice(groupKey,monitorId)
|
||||
const cameraInfo = await rebootCamera(onvifDevice)
|
||||
endData.onvifData = cameraInfo
|
||||
|
|
|
@ -21,6 +21,20 @@ module.exports = function(s,config,lang,app,io){
|
|||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/probe/:ke',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Authorized']
|
||||
});
|
||||
return
|
||||
}
|
||||
ffprobe(req.query.url,req.params.auth,(endData) => {
|
||||
s.closeJsonResponse(res,endData)
|
||||
})
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var os = require('os');
|
||||
const onvif = require("shinobi-onvif");
|
||||
const {
|
||||
addCredentialsToUrl,
|
||||
stringContains,
|
||||
getBuffer,
|
||||
} = require('../common.js')
|
||||
|
@ -145,9 +146,13 @@ module.exports = (s,config,lang) => {
|
|||
responseList.push(cameraResponse)
|
||||
var imageSnap
|
||||
try{
|
||||
const snapUri = (await device.services.media.getSnapshotUri({
|
||||
ProfileToken : device.current_profile.token,
|
||||
})).data.GetSnapshotUriResponse.MediaUri.Uri
|
||||
const snapUri = addCredentialsToUrl({
|
||||
username: onvifUsername,
|
||||
password: onvifPassword,
|
||||
url: (await device.services.media.getSnapshotUri({
|
||||
ProfileToken : device.current_profile.token,
|
||||
})).data.GetSnapshotUriResponse.MediaUri.Uri,
|
||||
});
|
||||
imageSnap = (await getBuffer(snapUri)).toString('base64');
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
|
|
|
@ -8,10 +8,11 @@ module.exports = function(s,config,lang,app,io){
|
|||
columns: "*",
|
||||
table: "Schedules"
|
||||
},(err,rows) => {
|
||||
rows.forEach(function(schedule){
|
||||
|
||||
rows && rows.forEach(function(schedule){
|
||||
s.updateSchedule(schedule)
|
||||
})
|
||||
if(callback)callback()
|
||||
if(callback && typeof callback === 'function')callback()
|
||||
})
|
||||
}
|
||||
//update schedule
|
||||
|
@ -193,9 +194,11 @@ module.exports = function(s,config,lang,app,io){
|
|||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
s.closeJsonResponse(res,endData)
|
||||
const {
|
||||
isSubAccount,
|
||||
} = s.checkPermission(user)
|
||||
if(isSubAccount){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not an Administrator Account']});
|
||||
return
|
||||
}
|
||||
var whereQuery = [
|
||||
|
@ -233,9 +236,11 @@ module.exports = function(s,config,lang,app,io){
|
|||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
s.closeJsonResponse(res,endData)
|
||||
const {
|
||||
isSubAccount,
|
||||
} = s.checkPermission(user)
|
||||
if(isSubAccount){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not an Administrator Account']});
|
||||
return
|
||||
}
|
||||
switch(req.params.action){
|
||||
|
|
|
@ -2,6 +2,9 @@ const fs = require('fs')
|
|||
const fetch = require('node-fetch')
|
||||
module.exports = function(s,config,lang,app,io){
|
||||
if(config.shinobiHubEndpoint === undefined){config.shinobiHubEndpoint = `https://hub.shinobi.video/`}else{config.shinobiHubEndpoint = s.checkCorrectPathEnding(config.shinobiHubEndpoint)}
|
||||
const {
|
||||
fetchWithAuthentication,
|
||||
} = require('./basic/utils.js')(process.cwd(),config)
|
||||
var stripUsernameAndPassword = function(string,username,password){
|
||||
if(username)string = string.split(username).join('_USERNAME_')
|
||||
if(password)string = string.split(password).join('_PASSWORD_')
|
||||
|
@ -53,20 +56,27 @@ module.exports = function(s,config,lang,app,io){
|
|||
json: JSON.stringify(monitorConfig)
|
||||
})
|
||||
if(validated.ok === true){
|
||||
request.post({
|
||||
url: `${config.shinobiHubEndpoint}api/${shinobiHubApiKey}/postConfiguration`,
|
||||
form: {
|
||||
"type": type,
|
||||
"brand": monitorConfig.ke,
|
||||
"name": monitorConfig.name,
|
||||
"description": "Backup at " + (new Date()),
|
||||
"json": validated.json,
|
||||
"details": JSON.stringify({
|
||||
// maybe ip address?
|
||||
})
|
||||
fetchWithAuthentication(
|
||||
`${config.shinobiHubEndpoint}api/${shinobiHubApiKey}/postConfiguration`,
|
||||
{
|
||||
method: 'POST',
|
||||
postData: {
|
||||
"type": type,
|
||||
"brand": monitorConfig.ke,
|
||||
"name": monitorConfig.name,
|
||||
"description": "Backup at " + (new Date()),
|
||||
"json": validated.json,
|
||||
"details": JSON.stringify({
|
||||
// maybe ip address?
|
||||
})
|
||||
}
|
||||
}
|
||||
}, function(err,httpResponse,body){
|
||||
callback(err,s.parseJSON(body) || {ok: false})
|
||||
).then(res => res.text())
|
||||
.then((data) => {
|
||||
callback(null,s.parseJSON(data) || {ok: false})
|
||||
})
|
||||
.catch((err) => {
|
||||
callback(err,{ok: false})
|
||||
})
|
||||
}else{
|
||||
callback(new Error(validated.msg),{ok: false})
|
||||
|
|
|
@ -367,7 +367,8 @@ module.exports = function(s,config,lang,io){
|
|||
return;
|
||||
}
|
||||
if((d.id||d.uid||d.mid)&&cn.ke){
|
||||
try{
|
||||
try{
|
||||
d.callbackResponse = {ok: true}
|
||||
switch(d.f){
|
||||
case'monitorOrder':
|
||||
if(d.monitorOrder && d.monitorOrder instanceof Object){
|
||||
|
@ -630,12 +631,6 @@ module.exports = function(s,config,lang,io){
|
|||
break;
|
||||
}
|
||||
break;
|
||||
case'control':
|
||||
ptzControl(d,function(msg){
|
||||
s.userLog(d,msg)
|
||||
tx({f:'control',response:msg})
|
||||
})
|
||||
break;
|
||||
case'jpeg_off':
|
||||
delete(cn.jpeg_on);
|
||||
if(cn.monitorsCurrentlyWatching){
|
||||
|
@ -699,6 +694,18 @@ module.exports = function(s,config,lang,io){
|
|||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
s.onOtherWebSocketMessagesExtensions.forEach(function(extender){
|
||||
extender(d,cn,tx)
|
||||
})
|
||||
break;
|
||||
}
|
||||
if(d.callbackId && !d.hasResponded){
|
||||
tx({
|
||||
f:'callback',
|
||||
callbackId: d.callbackId,
|
||||
args: [d.callbackResponse]
|
||||
})
|
||||
}
|
||||
}catch(er){
|
||||
s.systemLog('ERROR CATCH 1',er)
|
||||
|
|
14
libs/sql.js
14
libs/sql.js
|
@ -162,6 +162,20 @@ module.exports = function(s,config){
|
|||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
try{
|
||||
s.databaseEngine.schema.table('Videos', table => {
|
||||
table.string('objects')
|
||||
}).then(() => {
|
||||
console.log(`objects added to Videos table`)
|
||||
}).catch((err) => {
|
||||
if(err && err.code !== 'ER_DUP_FIELDNAME'){
|
||||
console.log('error')
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
delete(s.preQueries)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@ module.exports = function(s,config,lang,app,io){
|
|||
const {
|
||||
processKill,
|
||||
} = require('./monitor/utils.js')(s,config,lang)
|
||||
const {
|
||||
stitchMp4Files,
|
||||
} = require('./video/utils.js')(s,config,lang)
|
||||
const timelapseFramesCache = {}
|
||||
const timelapseFramesCacheTimeouts = {}
|
||||
s.getTimelapseFrameDirectory = function(e){
|
||||
|
@ -248,24 +251,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
})
|
||||
})
|
||||
}
|
||||
async function stitchMp4Files(options){
|
||||
return new Promise((resolve,reject) => {
|
||||
const concatListFile = options.listFile
|
||||
const finalMp4OutputLocation = options.output
|
||||
const commandString = `-y -threads 1 -f concat -safe 0 -i "${concatListFile}" -c:v copy -an -preset ultrafast "${finalMp4OutputLocation}"`
|
||||
s.debugLog("stitchMp4Files",commandString)
|
||||
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFPMEG(commandString))
|
||||
videoBuildProcess.stdout.on('data',function(data){
|
||||
s.debugLog('stdout',finalMp4OutputLocation,data.toString())
|
||||
})
|
||||
videoBuildProcess.stderr.on('data',function(data){
|
||||
s.debugLog('stderr',finalMp4OutputLocation,data.toString())
|
||||
})
|
||||
videoBuildProcess.on('exit',async function(data){
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
async function chunkFramesAndBuildMultipleVideosThenSticth(options){
|
||||
// a single video with too many frames makes the video unplayable, this is the fix.
|
||||
const frames = options.frames
|
||||
|
@ -311,10 +296,10 @@ module.exports = function(s,config,lang,app,io){
|
|||
listFile: concatListFile,
|
||||
output: finalMp4OutputLocation,
|
||||
})
|
||||
await fs.promises.unlink(concatListFile)
|
||||
for (let i = 0; i < filePathsList; i++) {
|
||||
await fs.promises.rm(concatListFile)
|
||||
for (let i = 0; i < filePathsList.length; i++) {
|
||||
var segmentFileOutput = filePathsList[i]
|
||||
await fs.promises.unlink(segmentFileOutput)
|
||||
await fs.promises.rm(segmentFileOutput)
|
||||
}
|
||||
s.debugLog('videoBuildProcess Stitching Complete!',finalMp4OutputLocation)
|
||||
}
|
||||
|
@ -400,6 +385,40 @@ module.exports = function(s,config,lang,app,io){
|
|||
response.msg = `${lang.Building}... ${lang['Please Wait...']}`
|
||||
return response
|
||||
}
|
||||
function initiateTimelapseVideoBuild({
|
||||
groupKey,
|
||||
monitorId,
|
||||
framesPerSecond,
|
||||
framesPosted,
|
||||
}){
|
||||
return new Promise((resolve,reject) => {
|
||||
let response = {ok: false}
|
||||
if(!monitorId){
|
||||
response.msg = lang['No Monitor Found, Ignoring Request']
|
||||
resolve(response)
|
||||
}else{
|
||||
const frames = []
|
||||
var n = 0
|
||||
framesPosted.forEach((frame) => {
|
||||
var firstParam = [['ke','=',groupKey],['mid','=',monitorId],['filename','=',frame.filename]]
|
||||
if(n !== 0)firstParam[0] = (['or']).concat(firstParam[0])
|
||||
frames.push(...firstParam)
|
||||
++n
|
||||
})
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Timelapse Frames",
|
||||
where: frames
|
||||
},async (err,r) => {
|
||||
if(r.length > 0){
|
||||
response = await createVideoFromTimelapse(r.reverse(),framesPerSecond)
|
||||
}
|
||||
resolve(response)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
// Web Paths
|
||||
// // // // //
|
||||
/**
|
||||
|
@ -409,25 +428,44 @@ module.exports = function(s,config,lang,app,io){
|
|||
config.webPaths.apiPrefix+':auth/timelapse/:ke',
|
||||
config.webPaths.apiPrefix+':auth/timelapse/:ke/:id',
|
||||
config.webPaths.apiPrefix+':auth/timelapse/:ke/:id/:date',
|
||||
config.webPaths.apiPrefix+':auth/cloudTimelapse/:ke',
|
||||
config.webPaths.apiPrefix+':auth/cloudTimelapse/:ke/:id',
|
||||
config.webPaths.apiPrefix+':auth/cloudTimelapse/:ke/:id/:date',
|
||||
], function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
|
||||
const monitorId = req.params.id
|
||||
const groupKey = req.params.ke
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
user.permissions.watch_videos==="0" ||
|
||||
hasRestrictions &&
|
||||
(
|
||||
!user.details.video_view ||
|
||||
user.details.video_view.indexOf(req.params.id) === -1
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && (
|
||||
monitorId && !monitorPermissions[`${monitorId}_video_view`] ||
|
||||
monitorRestrictions.length === 0
|
||||
)
|
||||
){
|
||||
s.closeJsonResponse(res,[])
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], frames: []});
|
||||
return
|
||||
}
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,req.params.id)
|
||||
var origURL = req.originalUrl.split('/')
|
||||
var videoParam = origURL[origURL.indexOf(req.params.auth) + 1]
|
||||
var dataSet = 'Timelapse Frames'
|
||||
switch(videoParam){
|
||||
case'cloudTimelapse':
|
||||
dataSet = 'Cloud Timelapse Frames'
|
||||
break;
|
||||
}
|
||||
s.getDatabaseRows({
|
||||
monitorRestrictions: monitorRestrictions,
|
||||
table: 'Timelapse Frames',
|
||||
table: dataSet,
|
||||
groupKey: req.params.ke,
|
||||
date: req.query.date,
|
||||
startDate: req.query.start,
|
||||
|
@ -453,47 +491,33 @@ module.exports = function(s,config,lang,app,io){
|
|||
], function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const actionParameter = !!req.params.action
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
user.permissions.watch_videos==="0" ||
|
||||
hasRestrictions &&
|
||||
(
|
||||
!user.details.video_view ||
|
||||
user.details.video_view.indexOf(req.params.id) === -1
|
||||
)
|
||||
isRestrictedApiKey && apiKeyPermissions.delete_videos_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_video_delete`]
|
||||
){
|
||||
s.closeJsonResponse(res,[])
|
||||
return
|
||||
}
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,req.params.id)
|
||||
if(monitorRestrictions.length === 0){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false
|
||||
})
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
const framesPerSecond = s.getPostData(req, 'fps')
|
||||
const framesPosted = s.getPostData(req, 'frames', true) || []
|
||||
const frames = []
|
||||
var n = 0
|
||||
framesPosted.forEach((frame) => {
|
||||
var firstParam = [['ke','=',req.params.ke],['mid','=',req.params.id],['filename','=',frame.filename]]
|
||||
if(n !== 0)firstParam[0] = (['or']).concat(firstParam[0])
|
||||
frames.push(...firstParam)
|
||||
++n
|
||||
})
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Timelapse Frames",
|
||||
where: frames
|
||||
},async (err,r) => {
|
||||
if(r.length === 0){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false
|
||||
})
|
||||
return
|
||||
}
|
||||
const buildResponse = await createVideoFromTimelapse(r.reverse(),s.getPostData(req, 'fps'))
|
||||
initiateTimelapseVideoBuild({
|
||||
groupKey,
|
||||
monitorId,
|
||||
framesPosted,
|
||||
framesPerSecond,
|
||||
}).then((buildResponse) => {
|
||||
s.closeJsonResponse(res,buildResponse)
|
||||
})
|
||||
},res,req);
|
||||
|
@ -507,15 +531,31 @@ module.exports = function(s,config,lang,app,io){
|
|||
], function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const actionParameter = !!req.params.action
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
user.permissions.watch_videos==="0" ||
|
||||
hasRestrictions && (!user.details.video_view || user.details.video_view.indexOf(req.params.id)===-1)
|
||||
actionParameter && (
|
||||
isRestrictedApiKey && apiKeyPermissions.delete_videos_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_video_delete`]
|
||||
) ||
|
||||
!actionParameter && (
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && monitorId && !monitorPermissions[`${monitorId}_video_view`]
|
||||
)
|
||||
){
|
||||
res.end(s.prettyPrint([]))
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,req.params.id)
|
||||
const cacheKey = req.params.ke + req.params.id + req.params.filename
|
||||
const processFrame = (frame) => {
|
||||
var fileLocation
|
||||
|
@ -529,7 +569,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
selectedDate = req.params.filename.split('T')[0]
|
||||
}
|
||||
fileLocation = `${fileLocation}${frame.ke}/${frame.mid}_timelapse/${selectedDate}/${req.params.filename}`
|
||||
if(req.params.action === 'delete'){
|
||||
if(actionParameter === 'delete'){
|
||||
deleteTimelapseFrame({
|
||||
ke: frame.ke,
|
||||
mid: frame.mid,
|
||||
|
@ -597,35 +637,63 @@ module.exports = function(s,config,lang,app,io){
|
|||
})
|
||||
},res,req);
|
||||
});
|
||||
var buildTimelapseVideos = function(){
|
||||
var dateNow = new Date()
|
||||
var hoursNow = dateNow.getHours()
|
||||
if(hoursNow === 1){
|
||||
var dateNowMoment = moment(dateNow).utc().format('YYYY-MM-DDTHH:mm:ss')
|
||||
var dateMinusOneDay = moment(dateNow).utc().subtract(1, 'days').format('YYYY-MM-DDTHH:mm:ss')
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Timelapse Frames",
|
||||
where: [
|
||||
['time','=>',dateMinusOneDay],
|
||||
['time','=<',dateNowMoment],
|
||||
]
|
||||
},function(err,frames) {
|
||||
var groups = {}
|
||||
frames.forEach(function(frame){
|
||||
if(groups[frame.ke])groups[frame.ke] = {}
|
||||
if(groups[frame.ke][frame.mid])groups[frame.ke][frame.mid] = []
|
||||
groups[frame.ke][frame.mid].push(frame)
|
||||
s.onOtherWebSocketMessages((d,connection) => {
|
||||
switch(d.f){
|
||||
case'timelapseVideoBuild':
|
||||
initiateTimelapseVideoBuild({
|
||||
groupKey: d.ke,
|
||||
monitorId: d.mid,
|
||||
framesPosted: d.frames,
|
||||
framesPerSecond: d.fps,
|
||||
}).then((buildResponse) => {
|
||||
s.tx({
|
||||
f: 'timelapse_build_requested',
|
||||
ke: d.ke,
|
||||
mid: d.mid,
|
||||
buildResponse: buildResponse,
|
||||
},'GRP_'+d.ke);
|
||||
})
|
||||
Object.keys(groups).forEach(function(groupKey){
|
||||
Object.keys(groups[groupKey]).forEach(async function(monitorId){
|
||||
var frameSet = groups[groupKey][monitorId]
|
||||
await createVideoFromTimelapse(frameSet,30)
|
||||
})
|
||||
})
|
||||
})
|
||||
break;
|
||||
}
|
||||
})
|
||||
function buildTimelapseVideos(){
|
||||
return new Promise((resolve,reject) => {
|
||||
var dateNow = new Date()
|
||||
var hoursNow = dateNow.getHours()
|
||||
if(hoursNow === 1){
|
||||
var dateNowMoment = moment(dateNow).utc().format('YYYY-MM-DDTHH:mm:ss')
|
||||
var dateMinusOneDay = moment(dateNow).utc().subtract(1, 'days').format('YYYY-MM-DDTHH:mm:ss')
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Timelapse Frames",
|
||||
where: [
|
||||
['time','=>',dateMinusOneDay],
|
||||
['time','=<',dateNowMoment],
|
||||
]
|
||||
},async function(err,frames) {
|
||||
var groups = {}
|
||||
frames.forEach(function(frame){
|
||||
if(groups[frame.ke])groups[frame.ke] = {}
|
||||
if(groups[frame.ke][frame.mid])groups[frame.ke][frame.mid] = []
|
||||
groups[frame.ke][frame.mid].push(frame)
|
||||
})
|
||||
const groupKeys = Object.keys(groups);
|
||||
for (let i = 0; i < groupKeys.length; i++) {
|
||||
const groupKey = groupKeys[i]
|
||||
const monitorIds = Object.keys(groups[groupKey]);
|
||||
for (let ii = 0; ii < monitorIds.length; ii++) {
|
||||
const monitorId = monitorIds[ii]
|
||||
const frameSet = groups[groupKey][monitorId]
|
||||
await createVideoFromTimelapse(frameSet,30)
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
})
|
||||
}else{
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
// Auto Build Timelapse Videos
|
||||
if(config.autoBuildTimelapseVideosDaily === true){
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
var fs = require('fs');
|
||||
module.exports = function(s,config,lang){
|
||||
//Amazon S3
|
||||
var beforeAccountSaveForAmazonS3 = function(d){
|
||||
function beforeAccountSave(d){
|
||||
//d = save event
|
||||
d.formDetails.aws_use_global=d.d.aws_use_global
|
||||
d.formDetails.use_aws_s3=d.d.use_aws_s3
|
||||
}
|
||||
var cloudDiskUseStartupForAmazonS3 = function(group,userDetails){
|
||||
function cloudDiskUseStartup(group,userDetails){
|
||||
group.cloudDiskUse['s3'].name = 'Amazon S3'
|
||||
group.cloudDiskUse['s3'].sizeLimitCheck = (userDetails.use_aws_s3_size_limit === '1')
|
||||
if(!userDetails.aws_s3_size_limit || userDetails.aws_s3_size_limit === ''){
|
||||
|
@ -15,7 +15,7 @@ module.exports = function(s,config,lang){
|
|||
group.cloudDiskUse['s3'].sizeLimit = parseFloat(userDetails.aws_s3_size_limit)
|
||||
}
|
||||
}
|
||||
var loadAmazonS3ForUser = function(e){
|
||||
function loadGroupApp(e){
|
||||
// e = user
|
||||
var userDetails = JSON.parse(e.details)
|
||||
if(userDetails.aws_use_global === '1' && config.cloudUploaders && config.cloudUploaders.AmazonS3){
|
||||
|
@ -54,11 +54,11 @@ module.exports = function(s,config,lang){
|
|||
s.group[e.ke].aws_s3 = new s.group[e.ke].aws.S3();
|
||||
}
|
||||
}
|
||||
var unloadAmazonS3ForUser = function(user){
|
||||
function unloadGroupApp(user){
|
||||
s.group[user.ke].aws = null
|
||||
s.group[user.ke].aws_s3 = null
|
||||
}
|
||||
var deleteVideoFromAmazonS3 = function(e,video,callback){
|
||||
function deleteVideo(e,video,callback){
|
||||
// e = user
|
||||
try{
|
||||
var videoDetails = JSON.parse(video.details)
|
||||
|
@ -68,6 +68,9 @@ module.exports = function(s,config,lang){
|
|||
if(!videoDetails.location){
|
||||
videoDetails.location = video.href.split('.amazonaws.com')[1]
|
||||
}
|
||||
if(videoDetails.type !== 's3'){
|
||||
return
|
||||
}
|
||||
s.group[e.ke].aws_s3.deleteObject({
|
||||
Bucket: s.group[e.ke].init.aws_s3_bucket,
|
||||
Key: videoDetails.location,
|
||||
|
@ -76,7 +79,7 @@ module.exports = function(s,config,lang){
|
|||
callback()
|
||||
});
|
||||
}
|
||||
var uploadVideoToAmazonS3 = function(e,k){
|
||||
function uploadVideo(e,k){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
|
@ -92,9 +95,8 @@ module.exports = function(s,config,lang){
|
|||
s.group[e.ke].aws_s3.upload({
|
||||
Bucket: s.group[e.ke].init.aws_s3_bucket,
|
||||
Key: saveLocation,
|
||||
Body:fileStream,
|
||||
ACL:'public-read',
|
||||
ContentType:'video/'+ext
|
||||
Body: fileStream,
|
||||
ContentType: 'video/'+ext
|
||||
},function(err,data){
|
||||
if(err){
|
||||
s.userLog(e,{type:lang['Amazon S3 Upload Error'],msg:err})
|
||||
|
@ -114,7 +116,7 @@ module.exports = function(s,config,lang){
|
|||
}),
|
||||
size: k.filesize,
|
||||
end: k.endTime,
|
||||
href: data.Location
|
||||
href: ''
|
||||
}
|
||||
})
|
||||
s.setCloudDiskUsedForGroup(e.ke,{
|
||||
|
@ -126,7 +128,7 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
}
|
||||
}
|
||||
var onInsertTimelapseFrame = function(monitorObject,queryInfo,filePath){
|
||||
function onInsertTimelapseFrame(monitorObject,queryInfo,filePath){
|
||||
var e = monitorObject
|
||||
if(s.group[e.ke].aws_s3 && s.group[e.ke].init.use_aws_s3 !== '0' && s.group[e.ke].init.aws_s3_save === '1'){
|
||||
var fileStream = fs.createReadStream(filePath)
|
||||
|
@ -152,6 +154,7 @@ module.exports = function(s,config,lang){
|
|||
mid: queryInfo.mid,
|
||||
ke: queryInfo.ke,
|
||||
time: queryInfo.time,
|
||||
filename: queryInfo.filename,
|
||||
details: s.s({
|
||||
type : 's3',
|
||||
location : saveLocation
|
||||
|
@ -169,7 +172,7 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
}
|
||||
}
|
||||
var onDeleteTimelapseFrameFromCloud = function(e,frame,callback){
|
||||
function onDeleteTimelapseFrameFromCloud(e,frame,callback){
|
||||
// e = user
|
||||
try{
|
||||
var frameDetails = JSON.parse(frame.details)
|
||||
|
@ -190,18 +193,30 @@ module.exports = function(s,config,lang){
|
|||
callback()
|
||||
});
|
||||
}
|
||||
function onGetVideoData(video){
|
||||
const videoDetails = s.parseJSON(video.details)
|
||||
return new Promise((resolve, reject) => {
|
||||
const saveLocation = videoDetails.location
|
||||
var fileStream = s.group[video.ke].aws_s3.getObject({
|
||||
Bucket: s.group[video.ke].init.aws_s3_bucket,
|
||||
Key: saveLocation,
|
||||
}).createReadStream();
|
||||
resolve(fileStream)
|
||||
})
|
||||
}
|
||||
//amazon s3
|
||||
s.addCloudUploader({
|
||||
name: 's3',
|
||||
loadGroupAppExtender: loadAmazonS3ForUser,
|
||||
unloadGroupAppExtender: unloadAmazonS3ForUser,
|
||||
insertCompletedVideoExtender: uploadVideoToAmazonS3,
|
||||
deleteVideoFromCloudExtensions: deleteVideoFromAmazonS3,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartupForAmazonS3,
|
||||
beforeAccountSave: beforeAccountSaveForAmazonS3,
|
||||
onAccountSave: cloudDiskUseStartupForAmazonS3,
|
||||
loadGroupAppExtender: loadGroupApp,
|
||||
unloadGroupAppExtender: unloadGroupApp,
|
||||
insertCompletedVideoExtender: uploadVideo,
|
||||
deleteVideoFromCloudExtensions: deleteVideo,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartup,
|
||||
beforeAccountSave: beforeAccountSave,
|
||||
onAccountSave: cloudDiskUseStartup,
|
||||
onInsertTimelapseFrame: onInsertTimelapseFrame,
|
||||
onDeleteTimelapseFrameFromCloud: onDeleteTimelapseFrameFromCloud
|
||||
onDeleteTimelapseFrameFromCloud: onDeleteTimelapseFrameFromCloud,
|
||||
onGetVideoData
|
||||
})
|
||||
//return fields that will appear in settings
|
||||
return {
|
||||
|
|
|
@ -118,6 +118,9 @@ module.exports = (s,config,lang,app,io) => {
|
|||
var deleteVideoFromGoogleDrive = function(groupKey,video,callback){
|
||||
// e = user
|
||||
var videoDetails = s.parseJSON(video.details)
|
||||
if(videoDetails.type !== 'googd'){
|
||||
return
|
||||
}
|
||||
s.group[groupKey].googleDrive.files.delete({
|
||||
fileId: videoDetails.id
|
||||
}, function(err, resp){
|
||||
|
|
|
@ -84,6 +84,9 @@ module.exports = function(s,config,lang){
|
|||
if(!videoDetails.location){
|
||||
videoDetails.location = video.href.split(locationUrl)[1]
|
||||
}
|
||||
if(videoDetails.type !== 'whcs'){
|
||||
return
|
||||
}
|
||||
s.group[e.ke].whcs.deleteObject({
|
||||
Bucket: s.group[e.ke].init.whcs_bucket,
|
||||
Key: videoDetails.location,
|
||||
|
@ -114,9 +117,8 @@ module.exports = function(s,config,lang){
|
|||
s.group[e.ke].whcs.upload({
|
||||
Bucket: bucketName,
|
||||
Key: saveLocation,
|
||||
Body:fileStream,
|
||||
ACL:'public-read',
|
||||
ContentType:'video/'+ext
|
||||
Body: fileStream,
|
||||
ContentType: 'video/'+ext
|
||||
},options,function(err,data){
|
||||
if(err){
|
||||
console.error(err)
|
||||
|
@ -177,7 +179,7 @@ module.exports = function(s,config,lang){
|
|||
mid: queryInfo.mid,
|
||||
ke: queryInfo.ke,
|
||||
time: queryInfo.time,
|
||||
filename: queryInfo.filename,
|
||||
filename: queryInfo.filename,
|
||||
details: s.s({
|
||||
type : 'whcs',
|
||||
location : saveLocation
|
||||
|
@ -232,6 +234,17 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
return cloudLink
|
||||
}
|
||||
function onGetVideoData(video){
|
||||
const videoDetails = s.parseJSON(video.details)
|
||||
return new Promise((resolve, reject) => {
|
||||
const saveLocation = videoDetails.location
|
||||
var fileStream = s.group[video.ke].whcs.getObject({
|
||||
Bucket: s.group[video.ke].init.whcs_bucket,
|
||||
Key: saveLocation,
|
||||
}).createReadStream();
|
||||
resolve(fileStream)
|
||||
})
|
||||
}
|
||||
//wasabi
|
||||
s.addCloudUploader({
|
||||
name: 'whcs',
|
||||
|
@ -243,7 +256,8 @@ module.exports = function(s,config,lang){
|
|||
beforeAccountSave: beforeAccountSaveForWasabiHotCloudStorage,
|
||||
onAccountSave: cloudDiskUseStartupForWasabiHotCloudStorage,
|
||||
onInsertTimelapseFrame: onInsertTimelapseFrame,
|
||||
onDeleteTimelapseFrameFromCloud: onDeleteTimelapseFrameFromCloud
|
||||
onDeleteTimelapseFrameFromCloud: onDeleteTimelapseFrameFromCloud,
|
||||
onGetVideoData
|
||||
})
|
||||
return {
|
||||
"evaluation": "details.use_whcs !== '0'",
|
||||
|
|
|
@ -94,7 +94,7 @@ module.exports = function(s,config,lang){
|
|||
}),
|
||||
size: k.filesize,
|
||||
end: k.endTime,
|
||||
href: webdavRemoteUrl
|
||||
href: ''
|
||||
}
|
||||
})
|
||||
s.setCloudDiskUsedForGroup(e.ke,{
|
||||
|
@ -157,6 +157,61 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
}
|
||||
}
|
||||
function onInsertTimelapseFrame(monitorObject,queryInfo,filePath){
|
||||
var e = monitorObject
|
||||
if(s.group[e.ke].aws_s3 && s.group[e.ke].init.use_aws_s3 !== '0' && s.group[e.ke].init.aws_s3_save === '1'){
|
||||
const saveLocation = s.group[e.ke].init.webdav_dir+e.ke+'/'+e.mid+'_timelapse/' + queryInfo.filename
|
||||
fs.createReadStream(filePath).pipe(wfs.createWriteStream(saveLocation))
|
||||
if(s.group[e.ke].init.aws_s3_log === '1'){
|
||||
s.knexQuery({
|
||||
action: "insert",
|
||||
table: "Cloud Timelapse Frames",
|
||||
insert: {
|
||||
mid: queryInfo.mid,
|
||||
ke: queryInfo.ke,
|
||||
time: queryInfo.time,
|
||||
filename: queryInfo.filename,
|
||||
details: s.s({
|
||||
type : 'webdav',
|
||||
location : saveLocation
|
||||
}),
|
||||
size: queryInfo.size,
|
||||
href: ''
|
||||
}
|
||||
})
|
||||
s.setCloudDiskUsedForGroup(e.ke,{
|
||||
amount : s.kilobyteToMegabyte(queryInfo.size),
|
||||
storageType : 'webdav'
|
||||
},'timelapseFrames')
|
||||
s.purgeCloudDiskForGroup(e,'webdav','timelapseFrames')
|
||||
}
|
||||
}
|
||||
}
|
||||
function onDeleteTimelapseFrameFromCloud(e,frame,callback){
|
||||
// e = user
|
||||
try{
|
||||
var frameDetails = JSON.parse(frame.details)
|
||||
}catch(err){
|
||||
var frameDetails = frame.details
|
||||
}
|
||||
if(frameDetails.type !== 'webdav'){
|
||||
return
|
||||
}
|
||||
if(!frameDetails.location){
|
||||
frameDetails.location = frame.href.split(locationUrl)[1]
|
||||
}
|
||||
s.group[e.ke].webdav.unlink(frameDetails.location, function(err) {
|
||||
if (err) console.log(frameDetails.location,err)
|
||||
callback()
|
||||
})
|
||||
}
|
||||
async function onGetVideoData(video){
|
||||
const wfs = s.group[video.ke].webdav
|
||||
const videoDetails = s.parseJSON(video.details)
|
||||
const saveLocation = videoDetails.location
|
||||
const fileStream = wfs.createReadStream(saveLocation);
|
||||
return fileStream
|
||||
}
|
||||
//webdav
|
||||
s.addCloudUploader({
|
||||
name: 'webdav',
|
||||
|
@ -167,6 +222,9 @@ module.exports = function(s,config,lang){
|
|||
cloudDiskUseStartupExtensions: cloudDiskUseStartupForWebDav,
|
||||
beforeAccountSave: beforeAccountSaveForWebDav,
|
||||
onAccountSave: cloudDiskUseStartupForWebDav,
|
||||
onInsertTimelapseFrame,
|
||||
onDeleteTimelapseFrameFromCloud,
|
||||
onGetVideoData
|
||||
})
|
||||
return {
|
||||
"evaluation": "details.use_webdav !== '0'",
|
||||
|
|
|
@ -219,15 +219,19 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
//change global size value
|
||||
s.group[e.ke].usedSpace += currentChange
|
||||
s.group[e.ke].usedSpace = s.group[e.ke].usedSpace < 0 ? 0 : s.group[e.ke].usedSpace
|
||||
switch(storageType){
|
||||
case'timelapeFrames':
|
||||
s.group[e.ke].usedSpaceTimelapseFrames += currentChange
|
||||
s.group[e.ke].usedSpaceTimelapseFrames = s.group[e.ke].usedSpaceTimelapseFrames < 0 ? 0 : s.group[e.ke].usedSpaceTimelapseFrames
|
||||
break;
|
||||
case'fileBin':
|
||||
s.group[e.ke].usedSpaceFilebin += currentChange
|
||||
s.group[e.ke].usedSpaceFilebin = s.group[e.ke].usedSpaceFilebin < 0 ? 0 : s.group[e.ke].usedSpaceFilebin
|
||||
break;
|
||||
default:
|
||||
s.group[e.ke].usedSpaceVideos += currentChange
|
||||
s.group[e.ke].usedSpaceVideos = s.group[e.ke].usedSpaceVideos < 0 ? 0 : s.group[e.ke].usedSpaceVideos
|
||||
break;
|
||||
}
|
||||
//remove value just used from queue
|
||||
|
|
|
@ -288,7 +288,10 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
const deleteTimelapseFrames = function(groupKey,callback){
|
||||
//run purge command
|
||||
if(s.group[groupKey].usedSpaceTimelapseFrames > (s.group[groupKey].sizeLimit * (s.group[groupKey].sizeLimitTimelapseFramesPercent / 100) * config.cron.deleteOverMaxOffset)){
|
||||
const maxSize = (s.group[groupKey].sizeLimit * (s.group[groupKey].sizeLimitTimelapseFramesPercent / 100) * config.cron.deleteOverMaxOffset);
|
||||
const currentlyUsedSize = s.group[groupKey].usedSpaceTimelapseFrames
|
||||
s.debugLog(`deleteTimelapseFrames`,`${currentlyUsedSize}/${maxSize}`)
|
||||
if(currentlyUsedSize > maxSize){
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
const fs = require('fs')
|
||||
const { spawn } = require('child_process')
|
||||
module.exports = (s,config,lang) => {
|
||||
const {
|
||||
splitForFFPMEG,
|
||||
} = require('../ffmpeg/utils.js')(s,config,lang)
|
||||
// orphanedVideoCheck : new function
|
||||
const checkIfVideoIsOrphaned = (monitor,videosDirectory,filename) => {
|
||||
const response = {ok: true}
|
||||
|
@ -64,10 +67,10 @@ module.exports = (s,config,lang) => {
|
|||
let listing = spawn('sh',[tempDirectory + 'orphanCheck.sh'])
|
||||
// const onData = options.onData ? options.onData : () => {}
|
||||
const onError = options.onError ? options.onError : s.systemLog
|
||||
const onExit = () => {
|
||||
const onExit = async () => {
|
||||
try{
|
||||
listing.kill('SIGTERM')
|
||||
fs.unlink(tempDirectory + 'orphanCheck.sh',() => {})
|
||||
await fs.promises.rm(tempDirectory + 'orphanCheck.sh')
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
|
@ -210,9 +213,105 @@ module.exports = (s,config,lang) => {
|
|||
})
|
||||
})
|
||||
}
|
||||
async function getVideosBasedOnTagFoundInMatrixOfAssociatedEvent({
|
||||
groupKey,
|
||||
monitorId,
|
||||
startTime,
|
||||
endTime,
|
||||
searchQuery,
|
||||
monitorRestrictions
|
||||
}){
|
||||
const initialEventQuery = [
|
||||
['ke','=',groupKey],
|
||||
['objects','LIKE',`%${searchQuery}%`],
|
||||
]
|
||||
if(monitorId)initialEventQuery.push(['mid','=',monitorId]);
|
||||
if(startTime)initialEventQuery.push(['time','>',startTime]);
|
||||
if(endTime)initialEventQuery.push(['end','<',endTime]);
|
||||
if(monitorRestrictions)initialEventQuery.push(monitorRestrictions);
|
||||
const videoSelectResponse = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Videos",
|
||||
orderBy: ['time','desc'],
|
||||
where: initialEventQuery
|
||||
});
|
||||
return videoSelectResponse
|
||||
}
|
||||
async function stitchMp4Files(options){
|
||||
return new Promise((resolve,reject) => {
|
||||
const concatListFile = options.listFile
|
||||
const finalMp4OutputLocation = options.output
|
||||
const commandString = `-y -threads 1 -f concat -safe 0 -i "${concatListFile}" -c:v copy -an -preset ultrafast "${finalMp4OutputLocation}"`
|
||||
s.debugLog("stitchMp4Files",commandString)
|
||||
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFPMEG(commandString))
|
||||
videoBuildProcess.stdout.on('data',function(data){
|
||||
s.debugLog('stdout',finalMp4OutputLocation,data.toString())
|
||||
})
|
||||
videoBuildProcess.stderr.on('data',function(data){
|
||||
s.debugLog('stderr',finalMp4OutputLocation,data.toString())
|
||||
})
|
||||
videoBuildProcess.on('exit',async function(data){
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
const fixingAlready = {}
|
||||
async function reEncodeVideoAndReplace(videoRow){
|
||||
return new Promise((resolve,reject) => {
|
||||
const response = {ok: true}
|
||||
const fixingId = `${videoRow.ke}${videoRow.mid}${videoRow.time}`
|
||||
if(fixingAlready[fixingId]){
|
||||
response.ok = false
|
||||
response.msg = lang['Already Processing']
|
||||
resolve(response)
|
||||
}else{
|
||||
const filename = s.formattedTime(videoRow.time)+'.'+videoRow.ext
|
||||
const tempFilename = s.formattedTime(videoRow.time)+'.reencoding.'+videoRow.ext
|
||||
const videoFolder = s.getVideoDirectory(videoRow)
|
||||
const inputFilePath = `${videoFolder}${filename}`
|
||||
const outputFilePath = `${videoFolder}${tempFilename}`
|
||||
const commandString = `-y -threads 1 -re -i "${inputFilePath}" -c:v copy -c:a copy -preset ultrafast "${outputFilePath}"`
|
||||
fixingAlready[fixingId] = true
|
||||
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFPMEG(commandString))
|
||||
videoBuildProcess.stdout.on('data',function(data){
|
||||
s.debugLog('stdout',outputFilePath,data.toString())
|
||||
})
|
||||
videoBuildProcess.stderr.on('data',function(data){
|
||||
s.debugLog('stderr',outputFilePath,data.toString())
|
||||
})
|
||||
videoBuildProcess.on('exit',async function(data){
|
||||
fixingAlready[fixingId] = false
|
||||
try{
|
||||
function failed(err){
|
||||
response.ok = false
|
||||
response.err = err
|
||||
resolve(response)
|
||||
}
|
||||
const newFileStats = await fs.promises.stat(outputFilePath)
|
||||
await fs.promises.rm(inputFilePath)
|
||||
let readStream = fs.createReadStream(outputFilePath);
|
||||
let writeStream = fs.createWriteStream(inputFilePath);
|
||||
readStream.pipe(writeStream);
|
||||
writeStream.on('finish', async () => {
|
||||
resolve(response)
|
||||
await fs.promises.rm(outputFilePath)
|
||||
});
|
||||
writeStream.on('error', failed);
|
||||
readStream.on('error', failed);
|
||||
}catch(err){
|
||||
failed()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
return {
|
||||
reEncodeVideoAndReplace,
|
||||
stitchMp4Files,
|
||||
orphanedVideoCheck: orphanedVideoCheck,
|
||||
scanForOrphanedVideos: scanForOrphanedVideos,
|
||||
cutVideoLength: cutVideoLength,
|
||||
getVideosBasedOnTagFoundInMatrixOfAssociatedEvent: getVideosBasedOnTagFoundInMatrixOfAssociatedEvent,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ module.exports = function(s,config,lang){
|
|||
ext: e.ext,
|
||||
status: 1,
|
||||
details: s.s(k.details),
|
||||
objects: k.objects || '',
|
||||
size: k.filesize,
|
||||
end: k.endTime,
|
||||
}
|
||||
|
@ -116,6 +117,9 @@ module.exports = function(s,config,lang){
|
|||
if(k.fileExists===true){
|
||||
//close video row
|
||||
k.details = k.details && k.details instanceof Object ? k.details : {}
|
||||
var listOEvents = activeMonitor.detector_motion_count || []
|
||||
var listOTags = listOEvents.filter(row => row.details.reason === 'object').map(row => row.details.matrices.map(matrix => matrix.tag).join(',')).join(',').split(',')
|
||||
if(listOTags)k.objects = [...new Set(listOTags)].join(',');
|
||||
k.stat = fs.statSync(k.dir+k.file)
|
||||
k.filesize = k.stat.size
|
||||
k.filesizeMB = parseFloat((k.filesize/1048576).toFixed(2))
|
||||
|
@ -137,6 +141,7 @@ module.exports = function(s,config,lang){
|
|||
ke: e.ke,
|
||||
filename: k.filename,
|
||||
filesize: k.filesize,
|
||||
objects: k.objects,
|
||||
time: s.timeObject(k.startTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
end: s.timeObject(k.endTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
@ -156,6 +161,7 @@ module.exports = function(s,config,lang){
|
|||
time: k.startTime,
|
||||
size: k.filesize,
|
||||
end: k.endTime,
|
||||
objects: k.objects,
|
||||
events: monitorEventsCounted && monitorEventsCounted.length > 0 ? monitorEventsCounted : null
|
||||
},'GRP_'+e.ke,'video_view')
|
||||
//purge over max
|
||||
|
|
|
@ -5,6 +5,9 @@ var exec = require('child_process').exec;
|
|||
var spawn = require('child_process').spawn;
|
||||
var execSync = require('child_process').execSync;
|
||||
module.exports = function(s,config,lang,app){
|
||||
const {
|
||||
deleteMonitorData,
|
||||
} = require('./monitor/utils.js')(s,config,lang)
|
||||
/**
|
||||
* API : Administrator : Edit Sub-Account (Account to share cameras with)
|
||||
*/
|
||||
|
@ -13,14 +16,16 @@ module.exports = function(s,config,lang,app){
|
|||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
s.closeJsonResponse(res,endData)
|
||||
const {
|
||||
isSubAccount,
|
||||
} = s.checkPermission(user)
|
||||
if(isSubAccount){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not an Administrator Account']});
|
||||
return
|
||||
}
|
||||
var form = s.getPostData(req)
|
||||
var uid = form.uid || s.getPostData(req,'uid',false)
|
||||
var mail = form.mail || s.getPostData(req,'mail',false)
|
||||
var mail = (form.mail || s.getPostData(req,'mail',false) || '').trim()
|
||||
if(form){
|
||||
var keys = ['details']
|
||||
form.details = s.parseJSON(form.details) || {"sub": 1, "allmonitors": "1"}
|
||||
|
@ -100,9 +105,11 @@ module.exports = function(s,config,lang,app){
|
|||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
s.closeJsonResponse(res,endData)
|
||||
const {
|
||||
isSubAccount,
|
||||
} = s.checkPermission(user)
|
||||
if(isSubAccount){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not an Administrator Account']});
|
||||
return
|
||||
}
|
||||
var form = s.getPostData(req) || {}
|
||||
|
@ -171,9 +178,11 @@ module.exports = function(s,config,lang,app){
|
|||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
s.closeJsonResponse(res,endData)
|
||||
const {
|
||||
isSubAccount,
|
||||
} = s.checkPermission(user)
|
||||
if(isSubAccount){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not an Administrator Account']});
|
||||
return
|
||||
}else{
|
||||
endData.ok = true
|
||||
|
@ -206,9 +215,11 @@ module.exports = function(s,config,lang,app){
|
|||
}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not an Administrator Account']
|
||||
s.closeJsonResponse(res,endData)
|
||||
const {
|
||||
isSubAccount,
|
||||
} = s.checkPermission(user)
|
||||
if(isSubAccount){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not an Administrator Account']});
|
||||
return
|
||||
}
|
||||
var form = s.getPostData(req)
|
||||
|
@ -287,7 +298,26 @@ module.exports = function(s,config,lang,app){
|
|||
}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
var hasRestrictions = user.details.sub && user.details.allmonitors !== '1'
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
userPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
userPermissions.monitor_create_disallowed ||
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_monitor_edit`]
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
if(req.params.f !== 'delete'){
|
||||
var form = s.getPostData(req)
|
||||
if(!form){
|
||||
|
@ -296,82 +326,46 @@ module.exports = function(s,config,lang,app){
|
|||
return
|
||||
}
|
||||
form.mid = req.params.id.replace(/[^\w\s]/gi,'').replace(/ /g,'')
|
||||
if(!user.details.sub ||
|
||||
user.details.allmonitors === '1' ||
|
||||
hasRestrictions && user.details.monitor_edit.indexOf(form.mid) >- 1 ||
|
||||
hasRestrictions && user.details.monitor_create === '1'){
|
||||
if(form && form.name){
|
||||
s.checkDetails(form)
|
||||
form.ke = req.params.ke
|
||||
s.addOrEditMonitor(form,function(err,endData){
|
||||
res.end(s.prettyPrint(endData))
|
||||
},user)
|
||||
}else{
|
||||
endData.msg = user.lang.monitorEditText1;
|
||||
res.end(s.prettyPrint(endData))
|
||||
}
|
||||
}else{
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
if(form && form.name){
|
||||
s.checkDetails(form)
|
||||
form.ke = req.params.ke
|
||||
s.addOrEditMonitor(form,function(err,endData){
|
||||
res.end(s.prettyPrint(endData))
|
||||
},user)
|
||||
}else{
|
||||
endData.msg = user.lang.monitorEditText1;
|
||||
res.end(s.prettyPrint(endData))
|
||||
}
|
||||
}else{
|
||||
if(!user.details.sub || user.details.allmonitors === '1' || user.details.monitor_edit.indexOf(req.params.id) > -1 || hasRestrictions && user.details.monitor_create === '1'){
|
||||
s.userLog({
|
||||
s.userLog({
|
||||
ke: req.params.ke,
|
||||
mid: req.params.id
|
||||
},{
|
||||
type: 'Monitor Deleted',
|
||||
msg: 'by user : '+user.uid
|
||||
});
|
||||
req.params.delete=1;
|
||||
s.camera('stop',req.params);
|
||||
s.tx({f:'monitor_delete',uid:user.uid,mid:req.params.id,ke:req.params.ke},'GRP_'+req.params.ke);
|
||||
s.knexQuery({
|
||||
action: "delete",
|
||||
table: "Monitors",
|
||||
where: {
|
||||
ke: req.params.ke,
|
||||
mid: req.params.id
|
||||
},{
|
||||
type: 'Monitor Deleted',
|
||||
msg: 'by user : '+user.uid
|
||||
});
|
||||
req.params.delete=1;s.camera('stop',req.params);
|
||||
s.tx({f:'monitor_delete',uid:user.uid,mid:req.params.id,ke:req.params.ke},'GRP_'+req.params.ke);
|
||||
s.knexQuery({
|
||||
action: "delete",
|
||||
table: "Monitors",
|
||||
where: {
|
||||
mid: req.params.id,
|
||||
}
|
||||
})
|
||||
if(req.query.deleteFiles === 'true'){
|
||||
deleteMonitorData(req.params.ke,req.params.id).then(() => {
|
||||
s.debugLog(`Deleted Monitor Data`,{
|
||||
ke: req.params.ke,
|
||||
mid: req.params.id,
|
||||
}
|
||||
})
|
||||
})
|
||||
// s.knexQuery({
|
||||
// action: "delete",
|
||||
// table: "Files",
|
||||
// where: {
|
||||
// ke: req.params.ke,
|
||||
// mid: req.params.id,
|
||||
// }
|
||||
// })
|
||||
if(req.query.deleteFiles === 'true'){
|
||||
//videos
|
||||
s.dir.addStorage.forEach(function(v,n){
|
||||
var videosDir = v.path+req.params.ke+'/'+req.params.id+'/'
|
||||
fs.stat(videosDir,function(err,stat){
|
||||
if(!err){
|
||||
s.file('deleteFolder',videosDir)
|
||||
}
|
||||
})
|
||||
})
|
||||
var videosDir = s.dir.videos+req.params.ke+'/'+req.params.id+'/'
|
||||
fs.stat(videosDir,function(err,stat){
|
||||
if(!err){
|
||||
s.file('deleteFolder',videosDir)
|
||||
}
|
||||
})
|
||||
//fileBin
|
||||
var binDir = s.dir.fileBin+req.params.ke+'/'+req.params.id+'/'
|
||||
fs.stat(binDir,function(err,stat){
|
||||
if(!err){
|
||||
s.file('deleteFolder',binDir)
|
||||
}
|
||||
})
|
||||
}
|
||||
endData.ok=true;
|
||||
endData.msg='Monitor Deleted by user : '+user.uid
|
||||
res.end(s.prettyPrint(endData))
|
||||
}else{
|
||||
endData.msg=user.lang['Not Permitted'];
|
||||
res.end(s.prettyPrint(endData))
|
||||
}
|
||||
endData.ok=true;
|
||||
endData.msg='Monitor Deleted by user : '+user.uid
|
||||
res.end(s.prettyPrint(endData))
|
||||
}
|
||||
},res,req)
|
||||
})
|
||||
|
@ -522,9 +516,18 @@ module.exports = function(s,config,lang,app){
|
|||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
s.closeJsonResponse(res,endData)
|
||||
const groupKey = req.params.ke
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
userPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
userPermissions.monitor_create_disallowed ||
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
s.knexQuery({
|
||||
|
@ -560,9 +563,18 @@ module.exports = function(s,config,lang,app){
|
|||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
s.closeJsonResponse(res,endData)
|
||||
const groupKey = req.params.ke
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
userPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
userPermissions.monitor_create_disallowed ||
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
var presetQueryVals = [req.params.ke,'monitorStates',req.params.stateName]
|
||||
|
|
|
@ -30,6 +30,10 @@ module.exports = function(s,config,lang,app,io){
|
|||
spawnSubstreamProcess,
|
||||
destroySubstreamProcess,
|
||||
} = require('./monitor/utils.js')(s,config,lang)
|
||||
const {
|
||||
reEncodeVideoAndReplace,
|
||||
getVideosBasedOnTagFoundInMatrixOfAssociatedEvent,
|
||||
} = require('./video/utils.js')(s,config,lang)
|
||||
s.renderPage = function(req,res,paths,passables,callback){
|
||||
passables.window = {}
|
||||
passables.data = req.params
|
||||
|
@ -461,6 +465,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
renderPage(config.renderPaths.super,{
|
||||
config: config,
|
||||
lang: lang,
|
||||
define: s.getDefinitonFile(config.language),
|
||||
$user: superLoginResponse.user,
|
||||
customAutoLoad: s.customAutoLoadTree,
|
||||
currentVersion: s.currentVersion,
|
||||
|
@ -642,8 +647,22 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.auth(req.params,function(user){
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,monitorId)
|
||||
if(user.details.sub && user.details.allmonitors === '0' && (user.permissions.get_monitors === "0" || monitorRestrictions.length === 0)){
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.get_monitors_disallowed ||
|
||||
isRestricted && (
|
||||
monitorId && !monitorPermissions[`${monitorId}_monitors`] ||
|
||||
monitorRestrictions.length === 0
|
||||
)
|
||||
){
|
||||
s.closeJsonResponse(res,[]);
|
||||
return
|
||||
}
|
||||
|
@ -747,8 +766,22 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.auth(req.params,(user) => {
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,monitorId)
|
||||
if(user.details.sub && user.details.allmonitors === '0' && (user.permissions.get_monitors === "0" || monitorRestrictions.length === 0)){
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.get_monitors_disallowed ||
|
||||
isRestricted && (
|
||||
monitorId && !monitorPermissions[`${monitorId}_monitors`] ||
|
||||
monitorRestrictions.length === 0
|
||||
)
|
||||
){
|
||||
s.closeJsonResponse(res,[]);
|
||||
return
|
||||
}
|
||||
|
@ -826,11 +859,19 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.auth(req.params,async (user) => {
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
userPermissions,
|
||||
apiKeyPermissions,
|
||||
isRestrictedApiKey,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
user.permissions.control_monitors === "0" ||
|
||||
user.details.sub &&
|
||||
user.details.allmonitors !== '1' &&
|
||||
user.details.monitor_edit.indexOf(monitorId) === -1
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_monitors`]
|
||||
){
|
||||
response.msg = user.lang['Not Permitted']
|
||||
}else{
|
||||
|
@ -855,64 +896,64 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.closeJsonResponse(res,response);
|
||||
},res,req);
|
||||
});
|
||||
/**
|
||||
* API : Merge Recorded Videos into one file
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/videosMerge/:ke', function (req,res){
|
||||
var failed = function(resp){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(s.prettyPrint(resp))
|
||||
}
|
||||
if(req.query.videos && req.query.videos !== ''){
|
||||
s.auth(req.params,function(user){
|
||||
var videosSelected = JSON.parse(req.query.videos)
|
||||
const whereQuery = []
|
||||
var didOne = false
|
||||
videosSelected.forEach(function(video){
|
||||
var time = s.nameToTime(video.filename)
|
||||
if(req.query.isUTC === 'true'){
|
||||
time = s.utcToLocal(time)
|
||||
}
|
||||
if(didOne){
|
||||
whereQuery.push(['or','ke','=',req.params.ke])
|
||||
}else{
|
||||
didOne = true
|
||||
whereQuery.push(['ke','=',req.params.ke])
|
||||
}
|
||||
whereQuery.push(
|
||||
['mid','=',video.mid],
|
||||
['time','=',time],
|
||||
)
|
||||
|
||||
})
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Videos",
|
||||
where: whereQuery
|
||||
},(err,r) => {
|
||||
var resp = {ok: false}
|
||||
if(r && r[0]){
|
||||
s.mergeRecordedVideos(r,req.params.ke,function(fullPath,filename){
|
||||
res.setHeader('Content-Disposition', 'attachment; filename="'+filename+'"')
|
||||
var file = fs.createReadStream(fullPath)
|
||||
file.on('close',function(){
|
||||
setTimeout(function(){
|
||||
s.file('delete',fullPath)
|
||||
},1000 * 60 * 3)
|
||||
res.end()
|
||||
})
|
||||
file.pipe(res)
|
||||
})
|
||||
}else{
|
||||
failed({ok:false,msg:'No Videos Found'})
|
||||
}
|
||||
})
|
||||
},res,req);
|
||||
}else{
|
||||
failed({ok:false,msg:'"videos" query variable is missing from request.'})
|
||||
}
|
||||
})
|
||||
// /**
|
||||
// * API : Merge Recorded Videos into one file
|
||||
// */
|
||||
// app.get(config.webPaths.apiPrefix+':auth/videosMerge/:ke', function (req,res){
|
||||
// var failed = function(resp){
|
||||
// res.setHeader('Content-Type', 'application/json');
|
||||
// res.end(s.prettyPrint(resp))
|
||||
// }
|
||||
// if(req.query.videos && req.query.videos !== ''){
|
||||
// s.auth(req.params,function(user){
|
||||
// var videosSelected = JSON.parse(req.query.videos)
|
||||
// const whereQuery = []
|
||||
// var didOne = false
|
||||
// videosSelected.forEach(function(video){
|
||||
// var time = s.nameToTime(video.filename)
|
||||
// if(req.query.isUTC === 'true'){
|
||||
// time = s.utcToLocal(time)
|
||||
// }
|
||||
// if(didOne){
|
||||
// whereQuery.push(['or','ke','=',req.params.ke])
|
||||
// }else{
|
||||
// didOne = true
|
||||
// whereQuery.push(['ke','=',req.params.ke])
|
||||
// }
|
||||
// whereQuery.push(
|
||||
// ['mid','=',video.mid],
|
||||
// ['time','=',time],
|
||||
// )
|
||||
//
|
||||
// })
|
||||
// s.knexQuery({
|
||||
// action: "select",
|
||||
// columns: "*",
|
||||
// table: "Videos",
|
||||
// where: whereQuery
|
||||
// },(err,r) => {
|
||||
// var resp = {ok: false}
|
||||
// if(r && r[0]){
|
||||
// s.mergeRecordedVideos(r,req.params.ke,function(fullPath,filename){
|
||||
// res.setHeader('Content-Disposition', 'attachment; filename="'+filename+'"')
|
||||
// var file = fs.createReadStream(fullPath)
|
||||
// file.on('close',function(){
|
||||
// setTimeout(function(){
|
||||
// s.file('delete',fullPath)
|
||||
// },1000 * 60 * 3)
|
||||
// res.end()
|
||||
// })
|
||||
// file.pipe(res)
|
||||
// })
|
||||
// }else{
|
||||
// failed({ok:false,msg:'No Videos Found'})
|
||||
// }
|
||||
// })
|
||||
// },res,req);
|
||||
// }else{
|
||||
// failed({ok:false,msg:'"videos" query variable is missing from request.'})
|
||||
// }
|
||||
// })
|
||||
/**
|
||||
* API : Get Videos
|
||||
*/
|
||||
|
@ -924,10 +965,27 @@ module.exports = function(s,config,lang,app,io){
|
|||
], function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
const userDetails = user.details
|
||||
const monitorId = req.params.id
|
||||
const groupKey = req.params.ke
|
||||
const hasRestrictions = userDetails.sub && userDetails.allmonitors !== '1';
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && (
|
||||
monitorId && !monitorPermissions[`${monitorId}_video_view`] ||
|
||||
monitorRestrictions.length === 0
|
||||
)
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], videos: []});
|
||||
return
|
||||
}
|
||||
var origURL = req.originalUrl.split('/')
|
||||
var videoParam = origURL[origURL.indexOf(req.params.auth) + 1]
|
||||
var videoSet = 'Videos'
|
||||
|
@ -952,11 +1010,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
endIsStartTo: !!req.query.endIsStartTo,
|
||||
parseRowDetails: false,
|
||||
rowName: 'videos',
|
||||
preliminaryValidationFailed: (
|
||||
user.permissions.watch_videos === "0" ||
|
||||
hasRestrictions &&
|
||||
(!userDetails.video_view || userDetails.video_view.indexOf(monitorId)===-1)
|
||||
)
|
||||
preliminaryValidationFailed: false
|
||||
},(response) => {
|
||||
if(response && response.videos){
|
||||
s.buildVideoLinks(response.videos,{
|
||||
|
@ -970,6 +1024,60 @@ module.exports = function(s,config,lang,app,io){
|
|||
},res,req);
|
||||
});
|
||||
/**
|
||||
* API : Get Videos
|
||||
*/
|
||||
app.get([
|
||||
config.webPaths.apiPrefix+':auth/videosByEventTag/:ke',
|
||||
config.webPaths.apiPrefix+':auth/videosByEventTag/:ke/:id'
|
||||
], function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
const searchQuery = s.getPostData(req,'search')
|
||||
const startTime = s.getPostData(req,'start')
|
||||
const endTime = s.getPostData(req,'end')
|
||||
const monitorId = req.params.id
|
||||
const groupKey = req.params.ke
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && (
|
||||
monitorId && !monitorPermissions[`${monitorId}_video_view`] ||
|
||||
monitorRestrictions.length === 0
|
||||
)
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], videos: []});
|
||||
return
|
||||
}
|
||||
getVideosBasedOnTagFoundInMatrixOfAssociatedEvent({
|
||||
groupKey,
|
||||
monitorId,
|
||||
startTime,
|
||||
endTime,
|
||||
searchQuery,
|
||||
monitorRestrictions,
|
||||
}).then((response) => {
|
||||
if(response && response.rows){
|
||||
s.buildVideoLinks(response.rows,{
|
||||
auth : req.params.auth,
|
||||
videoParam : 'videos',
|
||||
})
|
||||
}
|
||||
s.closeJsonResponse(res,{
|
||||
ok: true,
|
||||
videos: response.rows,
|
||||
})
|
||||
})
|
||||
},res,req);
|
||||
});
|
||||
/**
|
||||
* API : Get Events
|
||||
*/
|
||||
app.get([
|
||||
|
@ -978,17 +1086,28 @@ module.exports = function(s,config,lang,app,io){
|
|||
], function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
const userDetails = user.details
|
||||
const monitorId = req.params.id
|
||||
const groupKey = req.params.ke
|
||||
const hasRestrictions = userDetails.sub && userDetails.allmonitors !== '1';
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,monitorId)
|
||||
const preliminaryValidationFailed = (
|
||||
user.permissions.watch_videos === "0" ||
|
||||
hasRestrictions &&
|
||||
(!userDetails.video_view || userDetails.video_view.indexOf(monitorId)===-1)
|
||||
);
|
||||
if(req.query.onlyCount === '1' && !preliminaryValidationFailed){
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && (
|
||||
monitorId && !monitorPermissions[`${monitorId}_video_view`] ||
|
||||
monitorRestrictions.length === 0
|
||||
)
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], events: []});
|
||||
return
|
||||
}
|
||||
if(req.query.onlyCount === '1'){
|
||||
const response = {ok: true}
|
||||
s.knexQuery({
|
||||
action: "count",
|
||||
|
@ -1026,7 +1145,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
noFormat: true,
|
||||
noCount: true,
|
||||
rowName: 'events',
|
||||
preliminaryValidationFailed: preliminaryValidationFailed
|
||||
preliminaryValidationFailed: false
|
||||
},(response) => {
|
||||
res.end(s.prettyPrint(response))
|
||||
})
|
||||
|
@ -1042,10 +1161,21 @@ module.exports = function(s,config,lang,app,io){
|
|||
], function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
const userDetails = user.details
|
||||
const monitorId = req.params.id
|
||||
const groupKey = req.params.ke
|
||||
const hasRestrictions = userDetails.sub && userDetails.allmonitors !== '1';
|
||||
const monitorId = req.params.id
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
userPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
userPermissions.view_logs_disallowed ||
|
||||
isRestrictedApiKey && apiKeyPermissions.get_logs_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], logs: []});
|
||||
return
|
||||
}
|
||||
s.sqlQueryBetweenTimesWithPermissions({
|
||||
table: 'Logs',
|
||||
user: user,
|
||||
|
@ -1060,9 +1190,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
noFormat: true,
|
||||
noCount: true,
|
||||
rowName: 'logs',
|
||||
preliminaryValidationFailed: (
|
||||
user.permissions.get_logs === "0" || userDetails.sub && userDetails.view_logs !== '1'
|
||||
)
|
||||
preliminaryValidationFailed: false
|
||||
},(response) => {
|
||||
response.forEach(function(v,n){
|
||||
v.info = JSON.parse(v.info)
|
||||
|
@ -1072,51 +1200,30 @@ module.exports = function(s,config,lang,app,io){
|
|||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Get Monitors Online
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/smonitor/:ke', function (req,res){
|
||||
var response = {ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,(user) => {
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,monitorId)
|
||||
if(user.details.sub && user.details.allmonitors === '0' && (user.permissions.get_monitors === "0" || monitorRestrictions.length === 0)){
|
||||
s.closeJsonResponse(res,[]);
|
||||
return
|
||||
}
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Monitors",
|
||||
where: [
|
||||
['ke','=',groupKey],
|
||||
monitorRestrictions
|
||||
]
|
||||
},(err,r) => {
|
||||
const startedMonitors = []
|
||||
r.forEach(function(v){
|
||||
if(
|
||||
s.group[groupKey] &&
|
||||
s.group[groupKey].activeMonitors[v.mid] &&
|
||||
s.group[groupKey].activeMonitors[v.mid].isStarted === true
|
||||
){
|
||||
startedMonitors.push(v)
|
||||
}
|
||||
})
|
||||
s.closeJsonResponse(res,startedMonitors)
|
||||
})
|
||||
},res,req);
|
||||
});
|
||||
/**
|
||||
* API : Monitor Mode Controller
|
||||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f',config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f/:ff',config.webPaths.apiPrefix+':auth/monitor/:ke/:id/:f/:ff/:fff'], function (req,res){
|
||||
var response = {ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
if(user.permissions.control_monitors==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitor_edit.indexOf(req.params.id)===-1){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
userPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
userPermissions.monitor_create_disallowed ||
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_monitor_edit`]
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
if(req.params.f===''){response.msg = user.lang.monitorGetText1;res.end(s.prettyPrint(response));return}
|
||||
|
@ -1149,10 +1256,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
}
|
||||
r.mode=req.params.f;
|
||||
try{r.details=JSON.parse(r.details);}catch(er){}
|
||||
if(req.query.fps){
|
||||
r.fps=parseFloat(r.details.detector_trigger_record_fps)
|
||||
s.group[r.ke].activeMonitors[r.mid].currentState.detector_trigger_record_fps=r.fps
|
||||
}
|
||||
r.id=r.mid;
|
||||
s.knexQuery({
|
||||
action: "update",
|
||||
|
@ -1239,12 +1342,20 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.auth(req.params,function(user){
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,monitorId)
|
||||
if(user.details.sub && user.details.allmonitors === '0' && (user.permissions.watch_videos === "0" || monitorRestrictions.length === 0)){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Permitted']
|
||||
})
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_video_view`]
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
var time = s.nameToTime(req.params.file)
|
||||
|
@ -1265,16 +1376,18 @@ module.exports = function(s,config,lang,app,io){
|
|||
},(err,r) => {
|
||||
if(r&&r[0]){
|
||||
r = r[0]
|
||||
if(JSON.parse(r.details).type === 'googd' && s.cloudDiskUseOnGetVideoDataExtensions['googd']){
|
||||
s.cloudDiskUseOnGetVideoDataExtensions['googd'](r).then((dataPipe) => {
|
||||
const videoDetails = JSON.parse(r.details)
|
||||
const storageType = videoDetails.type
|
||||
const onGetVideoData = s.cloudDiskUseOnGetVideoDataExtensions[storageType]
|
||||
if(onGetVideoData){
|
||||
onGetVideoData(r).then((dataPipe) => {
|
||||
dataPipe.pipe(res)
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
res.end(user.lang['File Not Found in Database'])
|
||||
})
|
||||
}else{
|
||||
// req.pipe(request(r.href)).pipe(res)
|
||||
fetch(actualUrl).then(actual => {
|
||||
fetch(r.href).then(actual => {
|
||||
actual.headers.forEach((v, n) => res.setHeader(n, v));
|
||||
actual.body.pipe(res);
|
||||
})
|
||||
|
@ -1294,12 +1407,20 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.auth(req.params,function(user){
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,monitorId)
|
||||
if(user.details.sub && user.details.allmonitors === '0' && (user.permissions.watch_videos === "0" || monitorRestrictions.length === 0)){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Permitted']
|
||||
})
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_video_view`]
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
var time = s.nameToTime(req.params.file)
|
||||
|
@ -1359,26 +1480,35 @@ module.exports = function(s,config,lang,app,io){
|
|||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/motion/:ke/:id', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const monitorRestrictions = s.getMonitorRestrictions(user.details,monitorId)
|
||||
|
||||
if(user.details.sub && user.details.allmonitors === '0' && monitorRestrictions.length === 0){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Permitted']
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const d = {
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const monitorConfig = s.group[groupKey].rawMonitorConfigurations[monitorId]
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId);
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
!monitorConfig ||
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: !monitorConfig ? user.lang['Monitor or Key does not exist.'] : lang['Not Authorized']
|
||||
});
|
||||
return
|
||||
}
|
||||
let simulatedEvent = {
|
||||
id: req.params.id,
|
||||
ke: req.params.ke
|
||||
}
|
||||
|
||||
if(req.query.data){
|
||||
try{
|
||||
Object.assign(d, {details: s.parseJSON(req.query.data)});
|
||||
Object.assign(simulatedEvent, {details: s.parseJSON(req.query.data)});
|
||||
}catch(err){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
|
@ -1388,13 +1518,19 @@ module.exports = function(s,config,lang,app,io){
|
|||
}
|
||||
}
|
||||
// fallback for cameras that doesn't support JSON in query parameters ( i.e Sercom ICamera1000 will fail to save HTTP_Notifications as invalid url)
|
||||
else if( req.query.plug && req.query.name && req.query.reason && req.query.confidence) {
|
||||
Object.assign(d, {
|
||||
else if(req.query.plug && req.query.name && req.query.reason && req.query.confidence) {
|
||||
const {
|
||||
plug,
|
||||
reason,
|
||||
confidence,
|
||||
name,
|
||||
} = req.query;
|
||||
Object.assign(simulatedEvent,{
|
||||
details: {
|
||||
plug: req.query.plug,
|
||||
reason: req.query.reason,
|
||||
confidence: req.query.confidence,
|
||||
name: req.query.name
|
||||
plug,
|
||||
reason,
|
||||
confidence,
|
||||
name,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1405,51 +1541,21 @@ module.exports = function(s,config,lang,app,io){
|
|||
})
|
||||
return
|
||||
}
|
||||
if(!d.ke||!d.id||!s.group[d.ke]){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: user.lang['No Group with this key exists']
|
||||
})
|
||||
return
|
||||
}
|
||||
if(!s.group[d.ke].rawMonitorConfigurations[d.id]){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: user.lang['Monitor or Key does not exist.']
|
||||
})
|
||||
return
|
||||
}
|
||||
var details = s.group[d.ke].rawMonitorConfigurations[d.id].details
|
||||
var detectorHttpApi = details.detector_http_api
|
||||
var detectorOn = (details.detector === '1')
|
||||
switch(detectorHttpApi){
|
||||
case'0':
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: user.lang['Trigger Blocked']
|
||||
})
|
||||
return
|
||||
break;
|
||||
case'2':
|
||||
if(!detectorOn){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: user.lang['Trigger Blocked']
|
||||
})
|
||||
return
|
||||
}
|
||||
break;
|
||||
case'2':
|
||||
if(detectorOn){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: user.lang['Trigger Blocked']
|
||||
})
|
||||
return
|
||||
}
|
||||
break;
|
||||
if(
|
||||
detectorHttpApi === '0' ||
|
||||
detectorHttpApi === '2' && !detectorOn ||
|
||||
detectorHttpApi === '3' && detectorOn
|
||||
){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: user.lang['Trigger Blocked']
|
||||
})
|
||||
return;
|
||||
}
|
||||
triggerEvent(d)
|
||||
triggerEvent(simulatedEvent)
|
||||
s.closeJsonResponse(res,{
|
||||
ok: true,
|
||||
msg: user.lang['Trigger Successful']
|
||||
|
@ -1472,8 +1578,25 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.get(config.webPaths.apiPrefix+':auth/eventCountStatus/:ke/:id', function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
const monitorId = req.params.id
|
||||
const groupKey = req.params.ke
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && (
|
||||
monitorId && !monitorPermissions[`${monitorId}_video_view`] ||
|
||||
monitorRestrictions.length === 0
|
||||
)
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], counted: 0, tags: []});
|
||||
return
|
||||
}
|
||||
var selectedObject = s.group[req.params.ke].activeMonitors[req.params.id].eventsCounted
|
||||
|
@ -1493,10 +1616,27 @@ module.exports = function(s,config,lang,app,io){
|
|||
], function (req,res){
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
s.auth(req.params,function(user){
|
||||
const userDetails = user.details
|
||||
const monitorId = req.params.id
|
||||
const groupKey = req.params.ke
|
||||
var hasRestrictions = userDetails.sub && userDetails.allmonitors !== '1';
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.watch_videos_disallowed ||
|
||||
isRestricted && (
|
||||
monitorId && !monitorPermissions[`${monitorId}_video_view`] ||
|
||||
monitorRestrictions.length === 0
|
||||
)
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized'], videos: []});
|
||||
return
|
||||
}
|
||||
s.sqlQueryBetweenTimesWithPermissions({
|
||||
table: 'Events Counts',
|
||||
user: user,
|
||||
|
@ -1512,11 +1652,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
endIsStartTo: !!req.query.endIsStartTo,
|
||||
parseRowDetails: true,
|
||||
rowName: 'counts',
|
||||
preliminaryValidationFailed: (
|
||||
user.permissions.watch_videos === "0" ||
|
||||
hasRestrictions &&
|
||||
(!userDetails.video_view || userDetails.video_view.indexOf(monitorId)===-1)
|
||||
)
|
||||
preliminaryValidationFailed: false
|
||||
},(response) => {
|
||||
res.end(s.prettyPrint(response))
|
||||
})
|
||||
|
@ -1557,12 +1693,24 @@ module.exports = function(s,config,lang,app,io){
|
|||
var response = {ok:false}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_delete.indexOf(req.params.id)===-1){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.delete_videos_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_video_delete`]
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
var groupKey = req.params.ke
|
||||
var monitorId = req.params.id
|
||||
// req.query.overwrite === '1'
|
||||
if(s.group[groupKey] && s.group[groupKey].activeMonitors && s.group[groupKey].activeMonitors[monitorId]){
|
||||
var monitor = s.group[groupKey].rawMonitorConfigurations[monitorId]
|
||||
|
@ -1615,11 +1763,25 @@ module.exports = function(s,config,lang,app,io){
|
|||
config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id/:file/:mode',
|
||||
config.webPaths.apiPrefix+':auth/cloudVideos/:ke/:id/:file/:mode/:f'
|
||||
], function (req,res){
|
||||
var response = {ok:false};
|
||||
let response = { ok: false };
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
if(user.permissions.watch_videos==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.video_delete.indexOf(req.params.id)===-1){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
const monitorId = req.params.id
|
||||
const groupKey = req.params.ke
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.delete_videos_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_video_delete`]
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return
|
||||
}
|
||||
var time = s.nameToTime(req.params.file)
|
||||
|
@ -1635,8 +1797,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
videoSet = 'Cloud Videos'
|
||||
break;
|
||||
}
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
|
@ -1647,15 +1807,14 @@ module.exports = function(s,config,lang,app,io){
|
|||
['time','=',time]
|
||||
],
|
||||
limit: 1
|
||||
},(err,r) => {
|
||||
},async (err,r) => {
|
||||
if(r && r[0]){
|
||||
r=r[0];
|
||||
r.filename=s.formattedTime(r.time)+'.'+r.ext;
|
||||
var details = s.parseJSON(r.details) || {}
|
||||
switch(req.params.mode){
|
||||
case'fix':
|
||||
response.ok = true;
|
||||
s.video('fix',r)
|
||||
response = await reEncodeVideoAndReplace(r)
|
||||
break;
|
||||
case'status':
|
||||
r.f = 'video_edit'
|
||||
|
|
|
@ -10,6 +10,24 @@ var httpProxy = require('http-proxy');
|
|||
var proxy = httpProxy.createProxyServer({})
|
||||
var ejs = require('ejs');
|
||||
module.exports = function(s,config,lang,app){
|
||||
function cantLiveStreamPermission(user,monitorId,permission){
|
||||
const {
|
||||
monitorPermissions,
|
||||
monitorRestrictions,
|
||||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions[`${permission}_disallowed`] ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_monitors`]
|
||||
){
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
var noCache = function(res){
|
||||
res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate')
|
||||
res.setHeader('Expires', '-1')
|
||||
|
@ -21,6 +39,11 @@ module.exports = function(s,config,lang,app){
|
|||
app.get([config.webPaths.apiPrefix+':auth/embed/:ke/:id',config.webPaths.apiPrefix+':auth/embed/:ke/:id/:addon'], function (req,res){
|
||||
req.params.protocol=req.protocol;
|
||||
s.auth(req.params,function(user){
|
||||
const monitorId = req.params.id
|
||||
if(cantLiveStreamPermission(user,monitorId)){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return;
|
||||
}
|
||||
noCache(res)
|
||||
if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
|
@ -44,6 +67,11 @@ module.exports = function(s,config,lang,app){
|
|||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/mp4/:ke/:id/:channel/s.mp4',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/s.mp4',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/:channel/s.ts',config.webPaths.apiPrefix+':auth/mp4/:ke/:id/s.ts'], function (req, res) {
|
||||
s.auth(req.params,function(user){
|
||||
const monitorId = req.params.id
|
||||
if(cantLiveStreamPermission(user,monitorId)){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return;
|
||||
}
|
||||
if(!s.group[req.params.ke] || !s.group[req.params.ke].activeMonitors[req.params.id]){
|
||||
res.status(404);
|
||||
res.end('404 : Monitor not found');
|
||||
|
@ -108,6 +136,11 @@ module.exports = function(s,config,lang,app){
|
|||
res.end()
|
||||
}else{
|
||||
s.auth(req.params,function(user){
|
||||
const monitorId = req.params.id
|
||||
if(cantLiveStreamPermission(user,monitorId)){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return;
|
||||
}
|
||||
s.checkChildProxy(req.params,function(){
|
||||
if(s.group[req.params.ke]&&s.group[req.params.ke].activeMonitors[req.params.id]){
|
||||
if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
|
||||
|
@ -169,6 +202,11 @@ module.exports = function(s,config,lang,app){
|
|||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/hls/:ke/:id/:file',config.webPaths.apiPrefix+':auth/hls/:ke/:id/:channel/:file'], function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const monitorId = req.params.id
|
||||
if(cantLiveStreamPermission(user,monitorId)){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return;
|
||||
}
|
||||
s.checkChildProxy(req.params,function(){
|
||||
noCache(res)
|
||||
if(user.permissions.watch_stream==="0"||user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors.indexOf(req.params.id)===-1){
|
||||
|
@ -195,6 +233,11 @@ module.exports = function(s,config,lang,app){
|
|||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/jpeg/:ke/:id/s.jpg', function(req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const monitorId = req.params.id
|
||||
if(cantLiveStreamPermission(user,monitorId)){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return;
|
||||
}
|
||||
s.checkChildProxy(req.params,function(){
|
||||
noCache(res)
|
||||
if(user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors&&user.details.monitors.indexOf(req.params.id)===-1){
|
||||
|
@ -221,9 +264,10 @@ module.exports = function(s,config,lang,app){
|
|||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/icon/:ke/:id', function(req,res){
|
||||
s.auth(req.params,async (user) => {
|
||||
if(user.details.sub&&user.details.allmonitors!=='1'&&user.details.monitors&&user.details.monitors.indexOf(req.params.id)===-1){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
return
|
||||
const monitorId = req.params.id
|
||||
if(cantLiveStreamPermission(user,monitorId,'watch_snapshot')){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return;
|
||||
}
|
||||
const flags = req.query.noflags ? '' : req.query.flags || '-s 200x200'
|
||||
res.writeHead(200, {
|
||||
|
@ -245,6 +289,10 @@ module.exports = function(s,config,lang,app){
|
|||
*/
|
||||
app.get([config.webPaths.apiPrefix+':auth/flv/:ke/:id/s.flv',config.webPaths.apiPrefix+':auth/flv/:ke/:id/:channel/s.flv'], function(req,res) {
|
||||
s.auth(req.params,function(user){
|
||||
if(cantLiveStreamPermission(user,monitorId)){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return;
|
||||
}
|
||||
s.checkChildProxy(req.params,function(){
|
||||
noCache(res)
|
||||
var Emitter,chunkChannel
|
||||
|
@ -302,6 +350,10 @@ module.exports = function(s,config,lang,app){
|
|||
config.webPaths.apiPrefix+':auth/h264/:ke/:id'
|
||||
], function (req, res) {
|
||||
s.auth(req.params,function(user){
|
||||
if(cantLiveStreamPermission(user,monitorId)){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
return;
|
||||
}
|
||||
s.checkChildProxy(req.params,function(){
|
||||
noCache(res)
|
||||
if(!req.query.feed){req.query.feed='1'}
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
"connection-tester": "^0.2.0",
|
||||
"cws": "^2.0.0",
|
||||
"digest-fetch": "^1.2.1",
|
||||
"discord.js": "^13.6.0",
|
||||
"ejs": "^2.5.5",
|
||||
"discord.js": "^12.2.0",
|
||||
"ejs": "^2.7.4",
|
||||
"express": "^4.16.4",
|
||||
"express-fileupload": "^1.1.6-alpha.6",
|
||||
"express-fileupload": "^1.4.0",
|
||||
"fs-extra": "9.0.1",
|
||||
"ftp-srv": "^4.4.0",
|
||||
"googleapis": "^100.0.0",
|
||||
|
@ -27,7 +27,7 @@
|
|||
"jsonfile": "^3.0.1",
|
||||
"knex": "^0.21.4",
|
||||
"ldapauth-fork": "^5.0.2",
|
||||
"moment": "^2.27.0",
|
||||
"moment": "^2.29.4",
|
||||
"mp4frag": "^0.2.0",
|
||||
"mqtt": "^4.3.7",
|
||||
"mysql": "^2.18.1",
|
||||
|
@ -35,7 +35,7 @@
|
|||
"node-fetch": "^2.6.7",
|
||||
"node-onvif-events": "^2.0.5",
|
||||
"node-ssh": "^12.0.4",
|
||||
"node-telegram-bot-api": "^0.52.0",
|
||||
"node-telegram-bot-api": "^0.58.0",
|
||||
"nodemailer": "^6.4.11",
|
||||
"pam-diff": "^1.1.0",
|
||||
"path": "^0.12.7",
|
||||
|
@ -44,7 +44,7 @@
|
|||
"pushover-notifications": "^1.2.2",
|
||||
"sat": "^0.7.1",
|
||||
"shinobi-onvif": "0.1.9",
|
||||
"shinobi-sound-detection": "^0.1.8",
|
||||
"shinobi-sound-detection": "^0.1.13",
|
||||
"shinobi-zwave": "^1.0.11",
|
||||
"smtp-server": "^3.5.0",
|
||||
"socket.io": "^4.4.1",
|
||||
|
@ -57,49 +57,24 @@
|
|||
"shinobi": "camera.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/builders": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.11.0.tgz",
|
||||
"integrity": "sha512-ZTB8yJdJKrKlq44dpWkNUrAtEJEq0gqpb7ASdv4vmq6/mZal5kOv312hQ56I/vxwMre+VIkoHquNUAfnTbiYtg==",
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^4.2.0",
|
||||
"discord-api-types": "^0.26.0",
|
||||
"ts-mixer": "^6.0.0",
|
||||
"tslib": "^2.3.1",
|
||||
"zod": "^3.11.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@discordjs/collection": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.4.0.tgz",
|
||||
"integrity": "sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw==",
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz",
|
||||
"integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==",
|
||||
"deprecated": "no longer supported"
|
||||
},
|
||||
"node_modules/@sapphire/async-queue": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.0.tgz",
|
||||
"integrity": "sha512-z+CDw5X4UgIEpZL8KM+ThVx1i8V60HBg0l/oFewTNbQQeRDJHdVxHyJykv+SF1H+Rc8EkMS81VTWo95jVYgO/g==",
|
||||
"engines": {
|
||||
"node": ">=v14.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node_modules/@discordjs/form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz",
|
||||
"integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/base64-arraybuffer": {
|
||||
|
@ -143,36 +118,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz",
|
||||
"integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw=="
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"form-data": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-fetch/node_modules/form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
|
||||
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
|
@ -1364,45 +1309,71 @@
|
|||
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
|
||||
"integrity": "sha1-eAqZyE59YAJgNhURxId2E78k9rs="
|
||||
},
|
||||
"node_modules/discord-api-types": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.26.1.tgz",
|
||||
"integrity": "sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/discord.js": {
|
||||
"version": "13.6.0",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.6.0.tgz",
|
||||
"integrity": "sha512-tXNR8zgsEPxPBvGk3AQjJ9ljIIC6/LOPjzKwpwz8Y1Q2X66Vi3ZqFgRHYwnHKC0jC0F+l4LzxlhmOJsBZDNg9g==",
|
||||
"version": "12.2.0",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.2.0.tgz",
|
||||
"integrity": "sha512-Ueb/0SOsxXyqwvwFYFe0msMrGqH1OMqpp2Dpbplnlr4MzcRrFWwsBM9gKNZXPVBHWUKiQkwU8AihXBXIvTTSvg==",
|
||||
"deprecated": "no longer supported",
|
||||
"dependencies": {
|
||||
"@discordjs/builders": "^0.11.0",
|
||||
"@discordjs/collection": "^0.4.0",
|
||||
"@sapphire/async-queue": "^1.1.9",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/ws": "^8.2.2",
|
||||
"discord-api-types": "^0.26.0",
|
||||
"form-data": "^4.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"ws": "^8.4.0"
|
||||
"@discordjs/collection": "^0.1.5",
|
||||
"@discordjs/form-data": "^3.0.1",
|
||||
"abort-controller": "^3.0.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"prism-media": "^1.2.0",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"ws": "^7.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.6.0",
|
||||
"npm": ">=7.0.0"
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"erlpack": "discordapp/erlpack",
|
||||
"libsodium-wrappers": "^0.7.6",
|
||||
"sodium": "^3.0.2",
|
||||
"utf-8-validate": "^5.0.2",
|
||||
"zlib-sync": "^0.1.6"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"erlpack": {
|
||||
"optional": true
|
||||
},
|
||||
"libsodium-wrappers": {
|
||||
"optional": true
|
||||
},
|
||||
"sodium": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
},
|
||||
"zlib-sync": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/discord.js/node_modules/form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"node_modules/discord.js/node_modules/ws": {
|
||||
"version": "7.5.9",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
"node": ">=8.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/dstructs-array-constructors": {
|
||||
|
@ -3971,9 +3942,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/node-telegram-bot-api": {
|
||||
"version": "0.52.0",
|
||||
"resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.52.0.tgz",
|
||||
"integrity": "sha512-HOOHJ14OcuAWcVZQb5kth2lrWJeeOdaO7XFdYXcJT9Dxpznm7iZDHBq9ODLknDTE4dhDMDL6TsjjgYV468gtDQ==",
|
||||
"version": "0.58.0",
|
||||
"resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.58.0.tgz",
|
||||
"integrity": "sha512-DmP5wBON9stOiunvUw/NvTb1clMYvj+c3NnSqbPZdVd6hNkNRnM97eqPZIH4UsBJ+4n+XFGpU33dCzjqD1sv3A==",
|
||||
"dependencies": {
|
||||
"array.prototype.findindex": "^2.0.2",
|
||||
"bl": "^1.2.3",
|
||||
|
@ -4536,6 +4507,31 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/prism-media": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.4.tgz",
|
||||
"integrity": "sha512-eW7LXORkTCQznZs+eqe9VjGOrLBxcBPXgNyHXMTSRVhphvd/RrxgIR7WaWt4fkLuhshcdT5KHL88LAfcvS3f5g==",
|
||||
"peerDependencies": {
|
||||
"@discordjs/opus": "^0.8.0",
|
||||
"ffmpeg-static": "^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0",
|
||||
"node-opus": "^0.3.3",
|
||||
"opusscript": "^0.0.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@discordjs/opus": {
|
||||
"optional": true
|
||||
},
|
||||
"ffmpeg-static": {
|
||||
"optional": true
|
||||
},
|
||||
"node-opus": {
|
||||
"optional": true
|
||||
},
|
||||
"opusscript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
|
@ -5041,9 +5037,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/shinobi-sound-detection": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/shinobi-sound-detection/-/shinobi-sound-detection-0.1.12.tgz",
|
||||
"integrity": "sha512-HbHqHfJJmut4UWRlw+G1f+LIItycyLNdPW5oUd7g8mej+2PiSh0vUvt1ja/cryV0Hj+El9eZZp4xh9V/lFnA9A==",
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/shinobi-sound-detection/-/shinobi-sound-detection-0.1.13.tgz",
|
||||
"integrity": "sha512-QWGzkGENV5y+3Bc7DTAAggXaTWVr7UT+yRX65uKjtDEE9aqU5sW9sck0k/D74PaRS+p/vBaCYMCbyJH+q21FZA==",
|
||||
"dependencies": {
|
||||
"async": "3.2.0",
|
||||
"chai": "4.3.4",
|
||||
|
@ -5798,16 +5794,6 @@
|
|||
"tree-kill": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-mixer": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.1.tgz",
|
||||
"integrity": "sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg=="
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
|
@ -5819,6 +5805,11 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
|
||||
},
|
||||
"node_modules/type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
|
@ -6499,43 +6490,23 @@
|
|||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
|
||||
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "3.14.3",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.14.3.tgz",
|
||||
"integrity": "sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordjs/builders": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-0.11.0.tgz",
|
||||
"integrity": "sha512-ZTB8yJdJKrKlq44dpWkNUrAtEJEq0gqpb7ASdv4vmq6/mZal5kOv312hQ56I/vxwMre+VIkoHquNUAfnTbiYtg==",
|
||||
"requires": {
|
||||
"@sindresorhus/is": "^4.2.0",
|
||||
"discord-api-types": "^0.26.0",
|
||||
"ts-mixer": "^6.0.0",
|
||||
"tslib": "^2.3.1",
|
||||
"zod": "^3.11.6"
|
||||
}
|
||||
},
|
||||
"@discordjs/collection": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.4.0.tgz",
|
||||
"integrity": "sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw=="
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz",
|
||||
"integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ=="
|
||||
},
|
||||
"@sapphire/async-queue": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.3.0.tgz",
|
||||
"integrity": "sha512-z+CDw5X4UgIEpZL8KM+ThVx1i8V60HBg0l/oFewTNbQQeRDJHdVxHyJykv+SF1H+Rc8EkMS81VTWo95jVYgO/g=="
|
||||
},
|
||||
"@sindresorhus/is": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||
"integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="
|
||||
"@discordjs/form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz",
|
||||
"integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"@socket.io/base64-arraybuffer": {
|
||||
"version": "1.0.2",
|
||||
|
@ -6575,35 +6546,6 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz",
|
||||
"integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw=="
|
||||
},
|
||||
"@types/node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==",
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"form-data": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
|
||||
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/ws": {
|
||||
"version": "8.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||
"integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"abort-controller": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||
|
@ -7523,36 +7465,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"discord-api-types": {
|
||||
"version": "0.26.1",
|
||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.26.1.tgz",
|
||||
"integrity": "sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ=="
|
||||
},
|
||||
"discord.js": {
|
||||
"version": "13.6.0",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-13.6.0.tgz",
|
||||
"integrity": "sha512-tXNR8zgsEPxPBvGk3AQjJ9ljIIC6/LOPjzKwpwz8Y1Q2X66Vi3ZqFgRHYwnHKC0jC0F+l4LzxlhmOJsBZDNg9g==",
|
||||
"version": "12.2.0",
|
||||
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.2.0.tgz",
|
||||
"integrity": "sha512-Ueb/0SOsxXyqwvwFYFe0msMrGqH1OMqpp2Dpbplnlr4MzcRrFWwsBM9gKNZXPVBHWUKiQkwU8AihXBXIvTTSvg==",
|
||||
"requires": {
|
||||
"@discordjs/builders": "^0.11.0",
|
||||
"@discordjs/collection": "^0.4.0",
|
||||
"@sapphire/async-queue": "^1.1.9",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/ws": "^8.2.2",
|
||||
"discord-api-types": "^0.26.0",
|
||||
"form-data": "^4.0.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"ws": "^8.4.0"
|
||||
"@discordjs/collection": "^0.1.5",
|
||||
"@discordjs/form-data": "^3.0.1",
|
||||
"abort-controller": "^3.0.0",
|
||||
"node-fetch": "^2.6.0",
|
||||
"prism-media": "^1.2.0",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"ws": "^7.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
"ws": {
|
||||
"version": "7.5.9",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
|
||||
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
|
||||
"requires": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -9492,9 +9424,9 @@
|
|||
}
|
||||
},
|
||||
"node-telegram-bot-api": {
|
||||
"version": "0.52.0",
|
||||
"resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.52.0.tgz",
|
||||
"integrity": "sha512-HOOHJ14OcuAWcVZQb5kth2lrWJeeOdaO7XFdYXcJT9Dxpznm7iZDHBq9ODLknDTE4dhDMDL6TsjjgYV468gtDQ==",
|
||||
"version": "0.58.0",
|
||||
"resolved": "https://registry.npmjs.org/node-telegram-bot-api/-/node-telegram-bot-api-0.58.0.tgz",
|
||||
"integrity": "sha512-DmP5wBON9stOiunvUw/NvTb1clMYvj+c3NnSqbPZdVd6hNkNRnM97eqPZIH4UsBJ+4n+XFGpU33dCzjqD1sv3A==",
|
||||
"requires": {
|
||||
"array.prototype.findindex": "^2.0.2",
|
||||
"bl": "^1.2.3",
|
||||
|
@ -9934,6 +9866,12 @@
|
|||
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
|
||||
"integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw="
|
||||
},
|
||||
"prism-media": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.3.4.tgz",
|
||||
"integrity": "sha512-eW7LXORkTCQznZs+eqe9VjGOrLBxcBPXgNyHXMTSRVhphvd/RrxgIR7WaWt4fkLuhshcdT5KHL88LAfcvS3f5g==",
|
||||
"requires": {}
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
|
@ -10337,9 +10275,9 @@
|
|||
}
|
||||
},
|
||||
"shinobi-sound-detection": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/shinobi-sound-detection/-/shinobi-sound-detection-0.1.12.tgz",
|
||||
"integrity": "sha512-HbHqHfJJmut4UWRlw+G1f+LIItycyLNdPW5oUd7g8mej+2PiSh0vUvt1ja/cryV0Hj+El9eZZp4xh9V/lFnA9A==",
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/shinobi-sound-detection/-/shinobi-sound-detection-0.1.13.tgz",
|
||||
"integrity": "sha512-QWGzkGENV5y+3Bc7DTAAggXaTWVr7UT+yRX65uKjtDEE9aqU5sW9sck0k/D74PaRS+p/vBaCYMCbyJH+q21FZA==",
|
||||
"requires": {
|
||||
"async": "3.2.0",
|
||||
"chai": "4.3.4",
|
||||
|
@ -10917,16 +10855,6 @@
|
|||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="
|
||||
},
|
||||
"ts-mixer": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.1.tgz",
|
||||
"integrity": "sha512-hvE+ZYXuINrx6Ei6D6hz+PTim0Uf++dYbK9FFifLNwQj+RwKquhQpn868yZsCtJYiclZF1u8l6WZxxKi+vv7Rg=="
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
|
@ -10935,6 +10863,11 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
|
||||
},
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
|
@ -11523,11 +11456,6 @@
|
|||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
|
||||
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
|
||||
},
|
||||
"zod": {
|
||||
"version": "3.14.3",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.14.3.tgz",
|
||||
"integrity": "sha512-OzwRCSXB1+/8F6w6HkYHdbuWysYWnAF4fkRgKDcSFc54CE+Sv0rHXKfeNUReGCrHukm1LNpi6AYeXotznhYJbQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
12
package.json
12
package.json
|
@ -22,10 +22,10 @@
|
|||
"connection-tester": "^0.2.0",
|
||||
"cws": "^2.0.0",
|
||||
"digest-fetch": "^1.2.1",
|
||||
"discord.js": "^13.6.0",
|
||||
"ejs": "^2.5.5",
|
||||
"discord.js": "^12.2.0",
|
||||
"ejs": "^2.7.4",
|
||||
"express": "^4.16.4",
|
||||
"express-fileupload": "^1.1.6-alpha.6",
|
||||
"express-fileupload": "^1.4.0",
|
||||
"fs-extra": "9.0.1",
|
||||
"ftp-srv": "^4.4.0",
|
||||
"googleapis": "^100.0.0",
|
||||
|
@ -33,7 +33,7 @@
|
|||
"jsonfile": "^3.0.1",
|
||||
"knex": "^0.21.4",
|
||||
"ldapauth-fork": "^5.0.2",
|
||||
"moment": "^2.27.0",
|
||||
"moment": "^2.29.4",
|
||||
"mp4frag": "^0.2.0",
|
||||
"mqtt": "^4.3.7",
|
||||
"mysql": "^2.18.1",
|
||||
|
@ -41,7 +41,7 @@
|
|||
"node-fetch": "^2.6.7",
|
||||
"node-onvif-events": "^2.0.5",
|
||||
"node-ssh": "^12.0.4",
|
||||
"node-telegram-bot-api": "^0.52.0",
|
||||
"node-telegram-bot-api": "^0.58.0",
|
||||
"nodemailer": "^6.4.11",
|
||||
"pam-diff": "^1.1.0",
|
||||
"path": "^0.12.7",
|
||||
|
@ -50,7 +50,7 @@
|
|||
"pushover-notifications": "^1.2.2",
|
||||
"sat": "^0.7.1",
|
||||
"shinobi-onvif": "0.1.9",
|
||||
"shinobi-sound-detection": "^0.1.8",
|
||||
"shinobi-sound-detection": "^0.1.13",
|
||||
"shinobi-zwave": "^1.0.11",
|
||||
"smtp-server": "^3.5.0",
|
||||
"socket.io": "^4.4.1",
|
||||
|
|
|
@ -1,171 +1,52 @@
|
|||
#!/bin/bash
|
||||
echo "ARM CPU Installation is currently NOT supported! Jetson Nano with GPU enabled is currently only supported."
|
||||
echo "Jetson Nano may experience \"Unsupported Errors\", you may ignore them. Patches will be applied."
|
||||
if [[ ! $(head -1 /etc/nv_tegra_release) =~ R32.*4\.[34] ]] ; then
|
||||
echo "ERROR: not JetPack-4.4"
|
||||
exit 1
|
||||
fi
|
||||
DIR=$(dirname "${0}")
|
||||
|
||||
cudaCompute=$(cat /sys/module/tegra_fuse/parameters/tegra_chip_id)
|
||||
# 33 : Nano, TX1
|
||||
# 24 : TX2
|
||||
# 25 : Xavier NX and AGX Xavier
|
||||
|
||||
DIR=$(dirname $0)
|
||||
echo $DIR
|
||||
echo "Replacing package.json for tfjs 2.3.0..."
|
||||
wget -O $DIR/package.json https://cdn.shinobi.video/binaries/tensorflow/2.3.0/package.json
|
||||
cp "${DIR}/package-jetson.json" "${DIR}/package.json"
|
||||
|
||||
echo "Removing existing Tensorflow Node.js modules..."
|
||||
rm -rf $DIR/node_modules
|
||||
rm -rf "${DIR}/node_modules"
|
||||
|
||||
echo "Installing Yarn package manager"
|
||||
npm install yarn -g --unsafe-perm --force
|
||||
|
||||
installJetsonFlag=false
|
||||
installArmFlag=false
|
||||
installGpuFlag=false
|
||||
dontCreateKeyFlag=false
|
||||
[ -d "${DIR}/tfjs-tfjs-v2.3.0" ] && echo "Removing existing Tensorflow source directory" && rm -rf "${DIR}/tfjs-tfjs-v2.3.0"
|
||||
|
||||
while [ ! $# -eq 0 ]
|
||||
do
|
||||
case "$1" in
|
||||
--jetson)
|
||||
installJetsonFlag=true
|
||||
exit
|
||||
;;
|
||||
--arm)
|
||||
installArmFlag=true
|
||||
exit
|
||||
;;
|
||||
--gpu)
|
||||
installGpuFlag=true
|
||||
exit
|
||||
;;
|
||||
--dont-create-key)
|
||||
dontCreateKeyFlag=true
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
echo "Downloading Tensorflow source tarball"
|
||||
wget -O "${DIR}/tfjs-v2.3.0.tar.gz" https://github.com/tensorflow/tfjs/archive/refs/tags/tfjs-v2.3.0.tar.gz
|
||||
|
||||
if [ "$installJetsonFlag" = true ] && [ "$installArmFlag" = true ]; then
|
||||
echo "--jetson and --arm cannot both be set. Exiting..."
|
||||
exit -1
|
||||
fi
|
||||
echo "Extracting and preparing Tensorflow source"
|
||||
tar -xf tfjs-v2.3.0.tar.gz -C "${DIR}"
|
||||
(cd "${DIR}/tfjs-tfjs-v2.3.0/tfjs-node-gpu" || exit ; ./prep-gpu.sh)
|
||||
|
||||
if ([ "$installJetsonFlag" = true ] || [ "$installArmFlag" = true ]) && [ "$installGpuFlag" = true ]; then
|
||||
echo "--gpu flag cannot be set with --jetson or --arm. Exiting..."
|
||||
exit -2
|
||||
fi
|
||||
echo "Building Tensorflow Node GPU package"
|
||||
(cd "${DIR}/tfjs-tfjs-v2.3.0/tfjs-node-gpu" || exit ; yarn && yarn build)
|
||||
|
||||
nonInteractiveFlag=false
|
||||
if [ "$installJetsonFlag" = true ] || [ "$installArmFlag" = true ] || [ "$installGpuFlag" = true ]; then
|
||||
nonInteractiveFlag=true
|
||||
fi
|
||||
manualInstallRequirements() {
|
||||
npm install --unsafe-perm
|
||||
npm install @tensorflow/tfjs-backend-cpu@2.3.0 @tensorflow/tfjs-backend-webgl@2.3.0 @tensorflow/tfjs-converter@2.3.0 @tensorflow/tfjs-core@2.3.0 @tensorflow/tfjs-layers@2.3.0 @tensorflow/tfjs-node@2.3.0 --unsafe-perm
|
||||
}
|
||||
runRebuildCpu() {
|
||||
npm rebuild @tensorflow/tfjs-node --build-addon-from-source --unsafe-perm
|
||||
}
|
||||
echo "Removing Tensorflow source tarball"
|
||||
rm -f "${DIR}/tfjs-v2.3.0.tar.gz"
|
||||
|
||||
runRebuildGpu() {
|
||||
npm rebuild @tensorflow/tfjs-node-gpu --build-addon-from-source --unsafe-perm
|
||||
}
|
||||
echo "Installing Tensorflow addon"
|
||||
yarn
|
||||
|
||||
installJetson() {
|
||||
installGpuFlag=true
|
||||
npm install @tensorflow/tfjs-node-gpu@2.3.0 --unsafe-perm
|
||||
customBinaryLocation="node_modules/@tensorflow/tfjs-node-gpu/scripts/custom-binary.json"
|
||||
case cudaCompute in
|
||||
"33" ) # Nano and TX1
|
||||
echo '{"tf-lib": "https://cdn.shinobi.video/binaries/tensorflow/2.3.0/libtensorflow.tar.gz"}' > "$customBinaryLocation"
|
||||
;;
|
||||
"25" ) # Xavier NX and AGX Xavier
|
||||
echo '{"tf-lib": "https://cdn.shinobi.video/binaries/tensorflow/2.3.0-xavier/libtensorflow.tar.gz"}' > "$customBinaryLocation"
|
||||
;;
|
||||
* ) # default
|
||||
echo '{"tf-lib": "https://cdn.shinobi.video/binaries/tensorflow/2.3.0/libtensorflow.tar.gz"}' > "$customBinaryLocation"
|
||||
;;
|
||||
esac
|
||||
manualInstallRequirements
|
||||
chmod -R 777 .
|
||||
runRebuildGpu
|
||||
}
|
||||
echo "Clean Yarn cache"
|
||||
yarn cache clean --all
|
||||
|
||||
installGpuRoute() {
|
||||
installGpuFlag=true
|
||||
manualInstallRequirements
|
||||
npm install @tensorflow/tfjs-node-gpu@2.3.0 --unsafe-perm
|
||||
}
|
||||
|
||||
installNonGpuRoute() {
|
||||
manualInstallRequirements
|
||||
npm install @tensorflow/tfjs-node@2.3.0 --unsafe-perm
|
||||
runRebuildCpu
|
||||
}
|
||||
|
||||
|
||||
if [ "$nonInteractiveFlag" = false ]; then
|
||||
echo "Shinobi - Are you installing on Jetson Nano or Xavier?"
|
||||
echo "You must be on JetPack 4.4 for this plugin to install!"
|
||||
echo "(y)es or (N)o"
|
||||
read armCpu
|
||||
if [ "$armCpu" = "y" ] || [ "$armCpu" = "Y" ]; then
|
||||
# echo "Shinobi - Is it a Jetson Nano?"
|
||||
# echo "You must be on JetPack 4.4 for this plugin to install!"
|
||||
# echo "(y)es or (N)o"
|
||||
# read isItJetsonNano
|
||||
# echo "Shinobi - You may see Unsupported Errors, please wait while patches are applied."
|
||||
# if [ "$isItJetsonNano" = "y" ] || [ "$isItJetsonNano" = "Y" ]; then
|
||||
installJetson
|
||||
# else
|
||||
# installArm
|
||||
# fi
|
||||
else
|
||||
echo "Shinobi - Do you want to install TensorFlow.js with GPU support? "
|
||||
echo "You can run this installer again to change it."
|
||||
echo "(y)es or (N)o"
|
||||
read nodejsinstall
|
||||
if [ "$nodejsinstall" = "y" ] || [ "$nodejsinstall" = "Y" ]; then
|
||||
installGpuRoute
|
||||
else
|
||||
installNonGpuRoute
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if [ "$installJetsonFlag" = true ]; then
|
||||
installJetson
|
||||
fi
|
||||
#
|
||||
# if [ "$installArmFlag" = true ]; then
|
||||
# installArm
|
||||
# fi
|
||||
|
||||
if [ "$installGpuFlag" = true ]; then
|
||||
installGpuRoute
|
||||
else
|
||||
installNonGpuRoute
|
||||
fi
|
||||
fi
|
||||
# # npm audit fix --force
|
||||
if [ ! -e "$DIR/conf.json" ]; then
|
||||
dontCreateKeyFlag=false
|
||||
if [ ! -e "${DIR}/conf.json" ]; then
|
||||
echo "Creating conf.json"
|
||||
sudo cp $DIR/conf.sample.json $DIR/conf.json
|
||||
sudo cp "${DIR}/conf.sample.json" "${DIR}/conf.json"
|
||||
else
|
||||
echo "conf.json already exists..."
|
||||
fi
|
||||
|
||||
if [ "$dontCreateKeyFlag" = false ]; then
|
||||
tfjsBuildVal="cpu"
|
||||
if [ "$installGpuFlag" = true ]; then
|
||||
tfjsBuildVal="gpu"
|
||||
fi
|
||||
tfjsBuildVal="gpu"
|
||||
|
||||
echo "Adding Random Plugin Key to Main Configuration"
|
||||
node $DIR/../../tools/modifyConfigurationForPlugin.js tensorflow key=$(head -c 64 < /dev/urandom | sha256sum | awk '{print substr($1,1,60)}') tfjsBuild=$tfjsBuildVal
|
||||
fi
|
||||
echo "Adding Random Plugin Key to Main Configuration"
|
||||
node "${DIR}/../../tools/modifyConfigurationForPlugin.js" tensorflow key="$(head -c 64 < /dev/urandom | sha256sum | awk '{print substr($1,1,60)}')" tfjsBuild="${tfjsBuildVal}"
|
||||
|
||||
echo "TF_FORCE_GPU_ALLOW_GROWTH=true" > "$DIR/.env"
|
||||
echo "#CUDA_VISIBLE_DEVICES=0,2" >> "$DIR/.env"
|
||||
echo "TF_FORCE_GPU_ALLOW_GROWTH=true" > "${DIR}/.env"
|
||||
echo "#CUDA_VISIBLE_DEVICES=0,2" >> "${DIR}/.env"
|
||||
|
||||
echo "Instalation finished"
|
||||
echo "For plugin automatic start run: pm2 start shinobi-tensorflow.js && pm2 save"
|
||||
echo "Or run manualy: node shinobi-tensorflow.js"
|
||||
echo "To make Shinobi avare of new plugin, restart it by: pm2 restart camera"
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
"random-seedable": "^1.0.8",
|
||||
"seedrandom": "2.4.3",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io": "2.0.4",
|
||||
"socket.io-client": "1.7.4",
|
||||
"socket.io": "^4.4.1",
|
||||
"socket.io-client": "^4.4.1",
|
||||
"xor128": "^0.1.0"
|
||||
},
|
||||
"bin": "shinobi-tensorflow.js",
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
"description": "Object Detection plugin based on @tensorflow/tfjs-node",
|
||||
"main": "shinobi-tensorflow.js",
|
||||
"dependencies": {
|
||||
"@tensorflow-models/coco-ssd": "^2.2.2",
|
||||
"@tensorflow/tfjs-converter": "^3.13.0",
|
||||
"@tensorflow/tfjs-core": "^3.13.0",
|
||||
"@tensorflow/tfjs-layers": "^3.13.0",
|
||||
"@tensorflow/tfjs-node": "^3.13.0",
|
||||
"@tensorflow/tfjs-node-gpu": "^3.13.0",
|
||||
"@tensorflow/tfjs-backend-cpu": "^3.13.0",
|
||||
"@tensorflow/tfjs-backend-webgl": "^3.13.0",
|
||||
"@tensorflow-models/coco-ssd": "2.2.1",
|
||||
"@tensorflow/tfjs": "2.3.0",
|
||||
"@tensorflow/tfjs-backend-cpu": "2.3.0",
|
||||
"@tensorflow/tfjs-backend-webgl": "2.3.0",
|
||||
"@tensorflow/tfjs-converter": "2.3.0",
|
||||
"@tensorflow/tfjs-core": "2.3.0",
|
||||
"@tensorflow/tfjs-layers": "2.3.0",
|
||||
"@tensorflow/tfjs-node-gpu": "file:tfjs-tfjs-v2.3.0/tfjs-node-gpu",
|
||||
"moment": "^2.29.1",
|
||||
"dotenv": "^8.2.0",
|
||||
"cws": "^2.0.0",
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
const package = require(`../package.json`)
|
||||
const depKeys = Object.keys(package.dependencies)
|
||||
let endText = ``
|
||||
depKeys.forEach((key) => {
|
||||
endText += `${key} - https://www.npmjs.com/package/${key}\n`
|
||||
})
|
||||
console.log(endText)
|
|
@ -7,4 +7,5 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
#region_editor_live iframe,.canvas_holder canvas{border:0;position:absolute;left:0;top:0}
|
||||
#region_editor_live iframe,.canvas_holder canvas,#tab-regionEditor .grid{border:0;position:absolute;left:0;top:0}
|
||||
#tab-regionEditor .grid{width:100%;height:100%;}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
border-radius: 5px
|
||||
}
|
||||
|
||||
.tab-videoPlayer:hover .tab-videoPlayer-event-objects {
|
||||
.tab-videoPlayer-view-container:hover .tab-videoPlayer-event-objects {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -68,11 +68,16 @@
|
|||
top: 0;
|
||||
}
|
||||
.video-time-needle-event {
|
||||
border-left: 3px solid rgb(249 242 31 / 20%);
|
||||
border-left: 3px solid #fffb00;
|
||||
border-radius: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
.video-time-needle-seeker {
|
||||
border-left: 3px solid #f00;
|
||||
}
|
||||
.video-day-slice {
|
||||
height: 100%;
|
||||
background: #1f80f9;
|
||||
}
|
||||
.video-day-slice-spacer {
|
||||
background: rgba(0,0,0,0.3);
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.video-thumbnail img {
|
||||
height: 75px;
|
||||
border-radius: 10px;
|
||||
}
|
|
@ -430,3 +430,8 @@ ul.squeeze {
|
|||
.bg-dark .bootstrap-table .table td {
|
||||
color: #fff!important;
|
||||
}
|
||||
|
||||
.grid {
|
||||
background-image: repeating-linear-gradient(rgb(204 204 204 / 50%) 0 1px, transparent 1px 100%), repeating-linear-gradient(90deg, rgb(204 204 204 / 50%) 0 1px, transparent 1px 100%);
|
||||
background-size: 71px 71px;
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -1,4 +1,5 @@
|
|||
PNotify.prototype.options.styling = "fontawesome";
|
||||
var isSubAccount = !!$user.details.sub
|
||||
var loadedMonitors = {}
|
||||
var tabTree = null
|
||||
var pageLoadingData = {}
|
||||
|
@ -679,7 +680,7 @@ function permissionCheck(toCheck,monitorId){
|
|||
return false
|
||||
}
|
||||
|
||||
function drawMonitorListToSelector(jqTarget,selectFirst,showId){
|
||||
function drawMonitorListToSelector(jqTarget,selectFirst,showId,addAllMonitorsOption){
|
||||
var html = ''
|
||||
$.each(loadedMonitors,function(n,v){
|
||||
html += createOptionHtml({
|
||||
|
@ -687,7 +688,10 @@ function drawMonitorListToSelector(jqTarget,selectFirst,showId){
|
|||
label: v.name + (showId ? ` (${v.mid})` : ''),
|
||||
})
|
||||
})
|
||||
jqTarget.html(html)
|
||||
addAllMonitorsOption ? jqTarget.html(`
|
||||
<option value="">${lang['All Monitors']}</option>
|
||||
<optgroup label="${lang.Monitors}">${html}</optgroup>
|
||||
`) : jqTarget.html(html);
|
||||
if(selectFirst){
|
||||
jqTarget
|
||||
.find('option')
|
||||
|
@ -812,6 +816,12 @@ function downloadJSON(jsonData,filename){
|
|||
.attr('download',filename)
|
||||
[0].click()
|
||||
}
|
||||
function downloadFile(downloadUrl,fileName){
|
||||
var a = document.createElement('a')
|
||||
a.href = downloadUrl
|
||||
a.download = fileName
|
||||
a.click()
|
||||
}
|
||||
function convertKbToHumanSize(theNumber){
|
||||
var amount = theNumber / 1048576
|
||||
var unit = amount / 1000 >= 1000 ? 'TB' : amount >= 1000 ? 'GB' : 'MB'
|
||||
|
@ -819,21 +829,23 @@ function convertKbToHumanSize(theNumber){
|
|||
return `${number} ${unit}`
|
||||
}
|
||||
function drawIndicatorBar(item){
|
||||
var html = `<div id="indicator-${item.name}" class="mb-2">
|
||||
<div class="d-flex flex-row text-white mb-1">
|
||||
<div class="pr-2">
|
||||
<i class="fa fa-${item.icon}"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<small>${item.label}</small>
|
||||
var html = `<div class="box-wrapper " style>
|
||||
<div id="indicator-${item.name}" class="mb-2">
|
||||
<div class="d-flex flex-row text-white mb-1">
|
||||
<div class="pr-2">
|
||||
<i class="fa fa-${item.icon}"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<small>${item.label}</small>
|
||||
</div>
|
||||
<div>
|
||||
<small class="indicator-percent">0%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<small class="indicator-percent">0%</small>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-warning" role="progressbar" style="width: 0%;"></div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-warning" role="progressbar" style="width: 0%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
|
|
@ -75,6 +75,7 @@ $(document).ready(function(e){
|
|||
}
|
||||
],
|
||||
data: data.files.map((file) => {
|
||||
var href = getApiPrefix('fileBin') + '/' + selectedMonitor + '/' + file.name
|
||||
return {
|
||||
monitorName: `<b>${loadedMonitors[file.mid]?.name || file.mid}</b>`,
|
||||
name: file.name,
|
||||
|
@ -85,8 +86,8 @@ $(document).ready(function(e){
|
|||
`,
|
||||
size: convertKbToHumanSize(file.size),
|
||||
buttons: `
|
||||
<a class="btn btn-sm btn-primary" href="${file.href}" download title="${lang.Download}"><i class="fa fa-download"></i></a>
|
||||
${file.details.video ? `<a class="btn btn-sm btn-primary preview-video" href="${file.href}" title="${lang.Play}"><i class="fa fa-play"></i></a>` : ``}
|
||||
<a class="btn btn-sm btn-primary" href="${href}" download title="${lang.Download}"><i class="fa fa-download"></i></a>
|
||||
${file.details.video ? `<a class="btn btn-sm btn-primary preview-video" href="${href}" title="${lang.Play}"><i class="fa fa-play"></i></a>` : ``}
|
||||
`,
|
||||
}
|
||||
})
|
||||
|
|
|
@ -75,6 +75,6 @@ $(document).ready(function(){
|
|||
dontShowForOneWeek()
|
||||
return false;
|
||||
})
|
||||
console.log('Please support the Shinobi developement.')
|
||||
console.log('Please support the Shinobi development.')
|
||||
console.log('https://licenses.shinobi.video/subscribe')
|
||||
})
|
||||
|
|
|
@ -128,6 +128,7 @@ function buildLiveGridBlock(monitor){
|
|||
})
|
||||
return
|
||||
}
|
||||
var monitorId = monitor.mid
|
||||
var monitorDetails = safeJsonParse(monitor.details)
|
||||
var monitorLiveId = `monitor_live_${monitor.mid}`
|
||||
var subStreamChannel = monitor.subStreamChannel
|
||||
|
@ -135,6 +136,11 @@ function buildLiveGridBlock(monitor){
|
|||
var streamElement = buildStreamElementHtml(streamType)
|
||||
var streamBlockInfo = definitions['Monitor Stream Window']
|
||||
if(!loadedLiveGrids[monitor.mid])loadedLiveGrids[monitor.mid] = {}
|
||||
var quickLinkHtml = ''
|
||||
$.each(streamBlockInfo.quickLinks,function(n,button){
|
||||
if(button.eval && !eval(button.eval))return;
|
||||
quickLinkHtml += `<a title="${button.label}" class="btn btn-sm mr-1 badge btn-${button.class}"><i class="fa fa-${button.icon}"></i></a>`
|
||||
})
|
||||
var baseHtml = `<div
|
||||
id="${monitorLiveId}"
|
||||
data-ke="${monitor.ke}"
|
||||
|
@ -155,24 +161,13 @@ function buildLiveGridBlock(monitor){
|
|||
${streamElement}
|
||||
</div>
|
||||
</div>
|
||||
${streamBlockInfo.gridBlockAfterContentHtml || ''}
|
||||
${(streamBlockInfo.gridBlockAfterContentHtml || '').replace(`$QUICKLINKS`,quickLinkHtml)}
|
||||
<div class="mdl-overlay-menu-backdrop hidden">
|
||||
<ul class="mdl-overlay-menu list-group">`
|
||||
var buttons = streamBlockInfo.links
|
||||
if(!monitor.details.control === '1'){
|
||||
delete(buttons["Control"])
|
||||
}
|
||||
if(!permissionCheck('video_view',monitor.mid)){
|
||||
delete(buttons["Videos List"])
|
||||
delete(buttons["Time-lapse"])
|
||||
delete(buttons["Power Viewer"])
|
||||
delete(buttons["Calendar"])
|
||||
}
|
||||
if(!permissionCheck('monitor_edit',monitor.mid)){
|
||||
delete(buttons["Monitor Settings"])
|
||||
}
|
||||
$.each(buttons,function(n,v){
|
||||
baseHtml += `<li class="list-item cursor-pointer ${v.class}" title="${v.label}" ${v.attr}><i class="fa fa-${v.icon}"></i> ${v.label}</li>`
|
||||
$.each(buttons,function(n,button){
|
||||
if(button.eval && !eval(button.eval))return;
|
||||
baseHtml += `<li class="list-item cursor-pointer ${button.class}" title="${button.label}" ${button.attr}><i class="fa fa-${button.icon}"></i> ${button.label}</li>`
|
||||
})
|
||||
baseHtml += `</ul>
|
||||
</div>
|
||||
|
@ -182,15 +177,17 @@ function buildLiveGridBlock(monitor){
|
|||
function drawPtzControlsOnLiveGridBlock(monitorId){
|
||||
var monitorItem = $('#monitor_live_' + monitorId)
|
||||
var ptzControls = monitorItem.find('.PTZ_controls');
|
||||
var loadedMonitor = loadedMonitors[monitorId]
|
||||
var stopCommandOnRelease = loadedMonitor.details.control_stop === '2'
|
||||
if(ptzControls.length>0){
|
||||
ptzControls.remove()
|
||||
}else{
|
||||
var html = `<div class="PTZ_controls">
|
||||
<div class="pad">
|
||||
<div class="control top run-live-grid-monitor-ptz" data-ptz-control="up"></div>
|
||||
<div class="control left run-live-grid-monitor-ptz" data-ptz-control="left"></div>
|
||||
<div class="control right run-live-grid-monitor-ptz" data-ptz-control="right"></div>
|
||||
<div class="control bottom run-live-grid-monitor-ptz" data-ptz-control="down"></div>
|
||||
<div class="control top run-live-grid-monitor-ptz${stopCommandOnRelease ? `-move` : '' }" data-ptz-control="up"></div>
|
||||
<div class="control left run-live-grid-monitor-ptz${stopCommandOnRelease ? `-move` : '' }" data-ptz-control="left"></div>
|
||||
<div class="control right run-live-grid-monitor-ptz${stopCommandOnRelease ? `-move` : '' }" data-ptz-control="right"></div>
|
||||
<div class="control bottom run-live-grid-monitor-ptz${stopCommandOnRelease ? `-move` : '' }" data-ptz-control="down"></div>
|
||||
<div class="control middle run-live-grid-monitor-ptz" data-ptz-control="center"></div>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm btn-group-justified">
|
||||
|
@ -808,8 +805,9 @@ function signalCheckLiveStream(options){
|
|||
}
|
||||
$(document).ready(function(e){
|
||||
liveGrid
|
||||
.on('dblclick','.stream-hud',function(){
|
||||
$(this).parents('[data-mid]').find('[monitor="fullscreen"]').click();
|
||||
.on('dblclick','.stream-block',function(){
|
||||
var monitorItem = $(this).parents('[data-mid]');
|
||||
fullScreenLiveGridStream(monitorItem)
|
||||
})
|
||||
$('body')
|
||||
.resize(function(){
|
||||
|
@ -898,6 +896,18 @@ $(document).ready(function(e){
|
|||
var switchChosen = el.attr('data-ptz-control')
|
||||
runPtzCommand(monitorId,switchChosen)
|
||||
})
|
||||
.on('mousedown','.run-live-grid-monitor-ptz-move',function(){
|
||||
var el = $(this)
|
||||
var monitorId = el.parents('[data-mid]').attr('data-mid')
|
||||
var switchChosen = el.attr('data-ptz-control')
|
||||
runPtzMove(monitorId,switchChosen,true)
|
||||
})
|
||||
.on('mouseup','.run-live-grid-monitor-ptz-move',function(){
|
||||
var el = $(this)
|
||||
var monitorId = el.parents('[data-mid]').attr('data-mid')
|
||||
var switchChosen = el.attr('data-ptz-control')
|
||||
runPtzMove(monitorId,switchChosen,false)
|
||||
})
|
||||
.on('click','.run-monitor-detection-trigger-test',function(){
|
||||
var el = $(this)
|
||||
var monitorId = el.parents('[data-mid]').attr('data-mid')
|
||||
|
@ -936,7 +946,7 @@ $(document).ready(function(e){
|
|||
},700)
|
||||
})
|
||||
.on('gsresizestop', function(){
|
||||
resetAllLiveGridDimensionsInMemory()
|
||||
// resetAllLiveGridDimensionsInMemory()
|
||||
saveLiveGridBlockPositions()
|
||||
});
|
||||
addOnTabReopen('liveGrid', function () {
|
||||
|
@ -956,7 +966,10 @@ $(document).ready(function(e){
|
|||
case'video_build_success':
|
||||
d.status = 1
|
||||
d.mid = d.id || d.mid
|
||||
if(liveGridElements[d.mid] && liveGridElements[d.mid].streamElement)drawVideoCardToMiniList(d.mid,createVideoLinks(d),false)
|
||||
var monitorId = d.mid
|
||||
var videoTime = d.time
|
||||
loadedVideosInMemory[`${monitorId}${videoTime}`] = d
|
||||
if(liveGridElements[monitorId] && liveGridElements[monitorId].streamElement)drawVideoCardToMiniList(monitorId,createVideoLinks(d),false)
|
||||
break;
|
||||
case'monitor_watch_off':case'monitor_stopping':
|
||||
var monitorId = d.mid || d.id
|
||||
|
@ -1091,9 +1104,9 @@ $(document).ready(function(e){
|
|||
}
|
||||
dashboardSwitchCallbacks.dontShowDetection = function(toggleState){
|
||||
if(toggleState !== 1){
|
||||
window.dontShowDetection = true
|
||||
}else{
|
||||
window.dontShowDetection = false
|
||||
}else{
|
||||
window.dontShowDetection = true
|
||||
}
|
||||
}
|
||||
dashboardSwitchCallbacks.monitorMuteAudio = function(toggleState){
|
||||
|
|
|
@ -7,14 +7,17 @@ function generateId(x){
|
|||
t += p.charAt(Math.floor(Math.random() * p.length));
|
||||
return t;
|
||||
}
|
||||
function onSelectorChange(_this,parent){
|
||||
var el = $(_this)
|
||||
var theParam = el.attr('selector')
|
||||
var theValue = el.val()
|
||||
var theSelected = el.find('option:selected').text()
|
||||
parent.find(`.${theParam}_input`).hide()
|
||||
parent.find(`.${theParam}_${theValue}`).show()
|
||||
parent.find(`.${theParam}_text`).text(theSelected)
|
||||
function onSelectorChange(el){
|
||||
try{
|
||||
var theParam = el.attr('selector')
|
||||
var theValue = el.val()
|
||||
var theSelected = el.find('option:selected').text()
|
||||
loginForm.find(`.${theParam}_input`).hide()
|
||||
loginForm.find(`.${theParam}_${theValue}`).show()
|
||||
loginForm.find(`.${theParam}_text`).text(theSelected)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
if(!cachedMachineId){
|
||||
cachedMachineId = generateId(20)
|
||||
|
@ -22,7 +25,10 @@ if(!cachedMachineId){
|
|||
}
|
||||
$(document).ready(function(){
|
||||
$('#machineID').val(cachedMachineId)
|
||||
$('[selector]').change(onSelectorChange).change();
|
||||
$('[selector]').change(function(){
|
||||
var el = $(this)
|
||||
onSelectorChange(el)
|
||||
}).change();
|
||||
})
|
||||
loginForm.submit(function(e){
|
||||
$('#login-message').remove()
|
||||
|
|
|
@ -12,6 +12,7 @@ var copySettingsSelector = $('#copy_settings')
|
|||
var monitorPresetsSelection = $('#monitorPresetsSelection')
|
||||
var monitorPresetsNameField = $('#monitorPresetsName')
|
||||
var monitorGroupSelectors = $('#monitor_groups')
|
||||
var monitorGroupMutliTriggerSelectContainer = $('#monitor_group_detector_multi')
|
||||
var editorForm = monitorEditorWindow.find('form')
|
||||
var fieldsLoaded = {}
|
||||
var sections = {}
|
||||
|
@ -134,7 +135,6 @@ function generateDefaultMonitorSettings(){
|
|||
"detector_scale_y": "480",
|
||||
"detector_record_method": "sip",
|
||||
"detector_trigger": "1",
|
||||
"detector_trigger_record_fps": "",
|
||||
"detector_timeout": "0.5",
|
||||
"detector_send_video_length": "",
|
||||
"watchdog_reset": "1",
|
||||
|
@ -287,6 +287,14 @@ function getMonitorGroupsSelected(){
|
|||
})
|
||||
return monitorGroupsInSelection
|
||||
}
|
||||
function getMonitorTriggerGroupsSelected(){
|
||||
var monitorGroupsInSelection = []
|
||||
monitorGroupMutliTriggerSelectContainer.find('input:checked').each(function(n,v){
|
||||
var groupId = $(v).val()
|
||||
monitorGroupsInSelection.push(groupId)
|
||||
})
|
||||
return monitorGroupsInSelection
|
||||
}
|
||||
var differentiateMonitorConfig = function(firstConfig,secondConfig){
|
||||
console.log(firstConfig,secondConfig)
|
||||
var diffedConfig = {}
|
||||
|
@ -408,6 +416,7 @@ window.getMonitorEditFormFields = function(){
|
|||
monitorConfig.details = safeJsonParse(monitorConfig.details)
|
||||
monitorConfig.details.substream = getSubStreamChannelFields()
|
||||
monitorConfig.details.groups = getMonitorGroupsSelected()
|
||||
monitorConfig.details.group_detector_multi = getMonitorTriggerGroupsSelected()
|
||||
monitorConfig.details.input_map_choices = monitorSectionInputMapsave()
|
||||
// TODO : Input Maps and Stream Channels (does old way at the moment)
|
||||
|
||||
|
@ -437,12 +446,12 @@ function getAdditionalStreamChannelFields(tempID,channelId){
|
|||
}
|
||||
|
||||
addOnTabOpen('monitorSettings', function () {
|
||||
triggerSecondaryHideCheckOnAll()
|
||||
setFieldVisibility()
|
||||
drawMonitorSettingsSubMenu()
|
||||
})
|
||||
|
||||
addOnTabReopen('monitorSettings', function () {
|
||||
triggerSecondaryHideCheckOnAll()
|
||||
setFieldVisibility()
|
||||
drawMonitorSettingsSubMenu()
|
||||
})
|
||||
function drawInputMapHtml(options){
|
||||
|
@ -649,7 +658,7 @@ function importIntoMonitorEditor(options){
|
|||
${v.name} <span class="text-muted">(${v.id})</span>
|
||||
</div>
|
||||
<div class="pr-3">
|
||||
<span><input class="form-check-input no-abs mdl-switch__input form-check-input" type="checkbox" value="${v.id}" ${isSelected ? 'checked' : ''}/></span>
|
||||
<span><input class="form-check-input no-abs" ${b} type="checkbox" value="${v.id}" ${isSelected ? 'checked' : ''}/></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
@ -675,7 +684,7 @@ function importIntoMonitorEditor(options){
|
|||
}
|
||||
})
|
||||
monitorsForCopy.find('optgroup').html(tmp)
|
||||
triggerSecondaryHideCheckOnAll()
|
||||
setFieldVisibility()
|
||||
drawMonitorSettingsSubMenu()
|
||||
}
|
||||
//parse "Automatic" field in "Input" Section
|
||||
|
@ -897,27 +906,65 @@ var showInputMappingFields = function(showMaps){
|
|||
}else{
|
||||
el.hide()
|
||||
}
|
||||
triggerSecondaryHideCheckOnAll()
|
||||
setFieldVisibility()
|
||||
drawMonitorSettingsSubMenu()
|
||||
}
|
||||
var triggerSecondaryHideCheck = function(el){
|
||||
var key = el.attr('selector')
|
||||
var value = el.val();
|
||||
var triggerChange = el.attr('triggerchange')
|
||||
var triggerChangeIgnore = el.attr('triggerChangeIgnore')
|
||||
editorForm.find('.' + key + '_input').hide()
|
||||
editorForm.find('.' + key + '_' + value).show();
|
||||
editorForm.find('.' + key + '_text').text($(this).find('option:selected').text())
|
||||
if(triggerChange && triggerChange !== '' && !triggerChangeIgnore || (triggerChangeIgnore && triggerChangeIgnore.split(',').indexOf(value) === -1)){
|
||||
console.log(triggerChange)
|
||||
$(triggerChange).trigger('change')
|
||||
function setFieldVisibilityNewWay(){
|
||||
var validation = getMonitorEditFormFields()
|
||||
if(!validation.ok){
|
||||
return console.log('Failed setFieldVisibilityNewWay',new Error(),validation)
|
||||
}
|
||||
var monitorConfig = validation.monitorConfig
|
||||
var monitorDetails = safeJsonParse(monitorConfig.details)
|
||||
var commonChecks = {
|
||||
streamSectionCopyModeVisibilities: `monitorDetails.stream_vcodec === 'libx264' ||
|
||||
monitorDetails.stream_vcodec === 'libx265' ||
|
||||
monitorDetails.stream_vcodec === 'h264_nvenc' ||
|
||||
monitorDetails.stream_vcodec === 'hevc_nvenc' ||
|
||||
monitorDetails.stream_vcodec === 'no' ||
|
||||
|
||||
monitorDetails.stream_type === 'mjpeg' ||
|
||||
monitorDetails.stream_type === 'b64' ||
|
||||
((monitorDetails.stream_type === 'hls' || monitorDetails.stream_type === 'mp4') && monitorDetails.stream_vcodec !== 'copy') ||
|
||||
monitorDetails.stream_type === 'gif' ||
|
||||
monitorDetails.stream_type === 'flv'`
|
||||
}
|
||||
editorForm.find('[visibility-conditions]').each(function(n,v){
|
||||
var el = $(v)
|
||||
var visibilityConditions = el.attr('visibility-conditions')
|
||||
var response = true
|
||||
var commonCheck = commonChecks[visibilityConditions]
|
||||
if(commonCheck){
|
||||
response = eval(commonCheck)
|
||||
}else{
|
||||
response = eval(visibilityConditions)
|
||||
}
|
||||
if(response){
|
||||
el.show()
|
||||
}else{
|
||||
el.hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
function setFieldVisibilityOldWay(formElement){
|
||||
var listToShow = []
|
||||
formElement.find('[selector]').each(function(n,v){
|
||||
var el = $(this)
|
||||
var keyName = el.attr('selector')
|
||||
var value = el.val()
|
||||
var toShow = `${keyName}_${value}`
|
||||
listToShow.push(toShow)
|
||||
formElement.find(`.${keyName}_input`).hide()
|
||||
})
|
||||
for (let i = 0; i < listToShow.length; i++) {
|
||||
var item = listToShow[i];
|
||||
var elements = formElement.find(`[class*="${item}"]`)
|
||||
elements.show()
|
||||
}
|
||||
}
|
||||
var triggerSecondaryHideCheckOnAll = function(){
|
||||
monitorEditorWindow.find('[selector]').each(function(){
|
||||
var el = $(this);
|
||||
triggerSecondaryHideCheck(el)
|
||||
})
|
||||
function setFieldVisibility(){
|
||||
setFieldVisibilityOldWay(editorForm)
|
||||
setFieldVisibilityNewWay()
|
||||
}
|
||||
monitorStreamChannels.on('click','.delete',function(){
|
||||
$(this).parents('.stream-channel').remove()
|
||||
|
@ -927,49 +974,6 @@ monitorStreamChannels.on('click','.delete',function(){
|
|||
monitorEditorWindow.on('change','[channel-detail]',function(){
|
||||
monitorStreamChannelsave()
|
||||
})
|
||||
//////////////////
|
||||
monitorEditorWindow.on('change','[groups]',function(){
|
||||
var e={};
|
||||
var el = monitorEditorWindow.find('[groups]:checked');
|
||||
var selectedGroups = [];
|
||||
el.each(function(n,v){
|
||||
selectedGroups.push($(v).val())
|
||||
});
|
||||
monitorEditorWindow.find('[detail="groups"]').val(JSON.stringify(selectedGroups)).change()
|
||||
})
|
||||
monitorEditorWindow.on('change','[group_detector_multi]',function(){
|
||||
var e={};
|
||||
var el = monitorEditorWindow.find('[group_detector_multi]:checked');
|
||||
var selectedMultiTrigger=[];
|
||||
el.each(function(n,v){
|
||||
selectedMultiTrigger.push($(v).val())
|
||||
});
|
||||
monitorEditorWindow.find('[detail="group_detector_multi"]').val(JSON.stringify(selectedMultiTrigger)).change()
|
||||
})
|
||||
monitorEditorWindow.on('change','.detector_cascade_selection',function(){
|
||||
var e={};
|
||||
var el = monitorEditorWindow.find('.detector_cascade_selection:checked');
|
||||
var selectedCascades = {};
|
||||
el.each(function(n,v){
|
||||
selectedCascades[$(v).val()]={}
|
||||
});
|
||||
monitorEditorWindow.find('[detail="detector_cascades"]').val(JSON.stringify(selectedCascades)).change()
|
||||
})
|
||||
//monitorEditorWindow.on('change','.detector_cascade_selection',function(){
|
||||
// var e={};
|
||||
// e.details=monitorEditorWindow.find('[name="details"]')
|
||||
// try{
|
||||
// e.detailsVal=safeJsonParse(e.details.val())
|
||||
// }catch(err){
|
||||
// e.detailsVal={}
|
||||
// }
|
||||
// e.detailsVal.detector_cascades=[];
|
||||
// var el = monitorEditorWindow.find('.detector_cascade_selection:checked');
|
||||
// el.each(function(n,v){
|
||||
// e.detailsVal.detector_cascades.push($(v).val())
|
||||
// });
|
||||
// e.details.val(JSON.stringify(e.detailsVal))
|
||||
//})
|
||||
monitorEditorWindow.find('.probe-monitor-settings').click(function(){
|
||||
$.pB.submit(buildMonitorURL(),true)
|
||||
})
|
||||
|
@ -1035,7 +1039,8 @@ editorForm.find('[detail]').change(function(){
|
|||
})
|
||||
editorForm.on('change','[selector]',function(){
|
||||
var el = $(this);
|
||||
triggerSecondaryHideCheck(el)
|
||||
onSelectorChange(el,editorForm)
|
||||
setFieldVisibilityNewWay()
|
||||
drawMonitorSettingsSubMenu()
|
||||
});
|
||||
editorForm.find('[name="type"]').change(function(e){
|
||||
|
@ -1073,7 +1078,7 @@ editorForm.find('[name="type"]').change(function(e){
|
|||
$('.shinobi-detector_name').empty()
|
||||
$('.shinobi-detector_plug').hide()
|
||||
$('.shinobi-detector-invert').show()
|
||||
triggerSecondaryHideCheckOnAll()
|
||||
setFieldVisibility()
|
||||
drawMonitorSettingsSubMenu()
|
||||
}else{
|
||||
var pluginTitle = []
|
||||
|
@ -1089,7 +1094,7 @@ editorForm.find('[name="type"]').change(function(e){
|
|||
$('.shinobi-detector-invert').hide()
|
||||
$('.shinobi-detector_name').text(pluginTitle.join(', '))
|
||||
if(pluginNotice.length > 0)$('.shinobi-detector-msg').text(pluginNotice.join('<br>'))
|
||||
triggerSecondaryHideCheckOnAll()
|
||||
setFieldVisibility()
|
||||
drawMonitorSettingsSubMenu()
|
||||
}
|
||||
}
|
||||
|
@ -1128,7 +1133,7 @@ editorForm.find('[name="type"]').change(function(e){
|
|||
${hasSelectedMonitor ? `<ul class="json-to-block striped import-monitor-preset cursor-pointer">${jsonToHtmlBlock(humanizedMonitorKeys)}</ul>` : ''}
|
||||
</div>
|
||||
<div class="pr-3">
|
||||
<span><input class="form-check-input no-abs mdl-switch__input" type="checkbox" value="${preset.name}" ${hasSelectedMonitor ? 'checked' : ''}/></span>
|
||||
<span><input class="form-check-input no-abs" type="checkbox" value="${preset.name}" ${hasSelectedMonitor ? 'checked' : ''}/></span>
|
||||
</div>
|
||||
<div>
|
||||
<a class="badge btn btn-sm btn-danger delete-preset"><i class="fa fa-trash-o"></i></a>
|
||||
|
@ -1440,7 +1445,7 @@ editorForm.find('[name="type"]').change(function(e){
|
|||
var monitorConfig = validation.monitorConfig
|
||||
if(loadedMonitors[monitorConfig.mid]){
|
||||
deleteMonitors([monitorConfig],function(){
|
||||
resetMonitorEditor()
|
||||
openMonitorEditorPage()
|
||||
goBackOneTab()
|
||||
})
|
||||
}else{
|
||||
|
|
|
@ -155,8 +155,7 @@ function runPtzCommand(monitorId,switchChosen){
|
|||
break;
|
||||
default:
|
||||
mainSocket.f({
|
||||
f: 'monitor',
|
||||
ff: 'control',
|
||||
f: 'control',
|
||||
direction: switchChosen,
|
||||
id: monitorId,
|
||||
ke: $user.ke
|
||||
|
@ -164,6 +163,14 @@ function runPtzCommand(monitorId,switchChosen){
|
|||
break;
|
||||
}
|
||||
}
|
||||
function runPtzMove(monitorId,switchChosen,doMove){
|
||||
mainSocket.f({
|
||||
f: doMove ? 'startMove' : 'stopMove',
|
||||
direction: switchChosen,
|
||||
id: monitorId,
|
||||
ke: $user.ke
|
||||
})
|
||||
}
|
||||
function runTestDetectionTrigger(monitorId,callback){
|
||||
$.getJSON(getApiPrefix() + '/motion/'+$user.ke+'/'+monitorId+'/?data={"plug":"manual_trigger","name":"Manual Trigger","reason":"Manual","confidence":100}',function(d){
|
||||
debugLog(d)
|
||||
|
|
|
@ -32,7 +32,7 @@ $(document).ready(function(){
|
|||
},
|
||||
{
|
||||
value: 'onDetectorNoTriggerTimeout',
|
||||
label: lang['Detection Event'],
|
||||
label: lang['No Trigger'],
|
||||
},
|
||||
{
|
||||
value: 'onAccountSave',
|
||||
|
|
|
@ -300,11 +300,14 @@ $(document).ready(function(){
|
|||
submitTheForm()
|
||||
return false;
|
||||
})
|
||||
onWebSocketEvent(function(d){
|
||||
switch(d.f){
|
||||
case'init_success':
|
||||
drawMonitorListToSelector(monitorsList)
|
||||
break;
|
||||
}
|
||||
addOnTabOpen('onvifDeviceManager', function () {
|
||||
drawMonitorListToSelector(monitorsList)
|
||||
var monitorId = monitorsList.val()
|
||||
openOnvifDeviceManager(monitorId)
|
||||
})
|
||||
addOnTabReopen('onvifDeviceManager', function () {
|
||||
drawMonitorListToSelector(monitorsList)
|
||||
var monitorId = monitorsList.val()
|
||||
openOnvifDeviceManager(monitorId)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -53,6 +53,7 @@ $(document).ready(function(e){
|
|||
var theLocation = getLocationFromUri(options.uri)
|
||||
var pathLocation = theLocation.location
|
||||
var monitorConfigPartial = {
|
||||
name: pathLocation.hostname,
|
||||
mid: tempID + `${options.port}`,
|
||||
host: pathLocation.hostname,
|
||||
port: pathLocation.port,
|
||||
|
|
|
@ -16,6 +16,7 @@ $(document).ready(function(e){
|
|||
var monitorSlotPlaySpeeds = {}
|
||||
var currentlyPlayingVideos = {}
|
||||
var powerVideoMute = true
|
||||
var powerVideoCanAutoPlay = true
|
||||
var lastPowerVideoSelectedMonitors = []
|
||||
var extenders = {
|
||||
onVideoPlayerTimeUpdateExtensions: [],
|
||||
|
@ -48,6 +49,11 @@ $(document).ready(function(e){
|
|||
});
|
||||
// fix utc/localtime translation (use timelapseJpeg as guide, it works as expected) />
|
||||
function loadVideosToTimeLineMemory(monitorId,videos,events){
|
||||
videos.forEach((video) => {
|
||||
createVideoLinks(video,{
|
||||
hideRemote: true
|
||||
})
|
||||
})
|
||||
powerVideoLoadedVideos[monitorId] = videos
|
||||
powerVideoLoadedEvents[monitorId] = events
|
||||
}
|
||||
|
@ -357,6 +363,15 @@ $(document).ready(function(e){
|
|||
// .off("play").on("play",function(){
|
||||
// console.log(monitorId,'play')
|
||||
// })
|
||||
.off("loadedmetadata").on("loadedmetadata",function(){
|
||||
resetWidthForActiveVideoPlayers()
|
||||
})
|
||||
.off("pause").on("pause",function(){
|
||||
resetWidthForActiveVideoPlayers()
|
||||
})
|
||||
.off("play").on("play",function(){
|
||||
resetWidthForActiveVideoPlayers()
|
||||
})
|
||||
.off("timeupdate").on("timeupdate",function(){
|
||||
try{
|
||||
var event = eventsLabeledByTime[monitorId][video.time][parseInt(this.currentTime)]
|
||||
|
@ -439,6 +454,14 @@ $(document).ready(function(e){
|
|||
motionMeterProgressBar.css('width','0')
|
||||
motionMeterProgressBarTextBox.text('0')
|
||||
}
|
||||
function resetWidthForActiveVideoPlayers(){
|
||||
var numberOfMonitors = 0
|
||||
powerVideoMonitorViewsElement.find(`.videoPlayer .videoNow`).each(function(n,videoEl){
|
||||
if(videoEl.currentTime > 0)numberOfMonitors += 1
|
||||
})
|
||||
var widthOfBlock = 100 / numberOfMonitors
|
||||
powerVideoMonitorViewsElement.find('.videoPlayer').css('width',`${widthOfBlock}%`)
|
||||
}
|
||||
function loadVideoIntoMonitorSlot(video,selectedTime){
|
||||
if(!video)return
|
||||
resetVisualDetectionDataForMonitorSlot(video.mid)
|
||||
|
@ -529,8 +552,15 @@ $(document).ready(function(e){
|
|||
videoNow.setAttribute('preload',true)
|
||||
videoNow.muted = true
|
||||
videoNow.playbackRate = monitorSlotPlaySpeeds[video.mid] || 1
|
||||
videoNow.currentTime = timeToStartAt / 1000
|
||||
try{
|
||||
videoNow.currentTime = timeToStartAt / 1000
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
videoNow.play()
|
||||
setTimeout(function(){
|
||||
resetWidthForActiveVideoPlayers()
|
||||
},1400)
|
||||
extenders.onVideoPlayerCreateExtensions.forEach(function(extender){
|
||||
extender(videoElement,watchPoint)
|
||||
})
|
||||
|
@ -678,9 +708,11 @@ $(document).ready(function(e){
|
|||
toggleZoomAllSlots()
|
||||
break;
|
||||
case'playAll':
|
||||
powerVideoCanAutoPlay = true
|
||||
playAllSlots()
|
||||
break;
|
||||
case'pauseAll':
|
||||
powerVideoCanAutoPlay = false
|
||||
pauseAllSlots()
|
||||
break;
|
||||
case'playSpeedAll':
|
||||
|
@ -701,6 +733,27 @@ $(document).ready(function(e){
|
|||
addOnTabOpen('powerVideo', function () {
|
||||
drawMonitorsList()
|
||||
})
|
||||
addOnTabReopen('powerVideo', function () {
|
||||
if(powerVideoCanAutoPlay){
|
||||
powerVideoWindow.find('video').each(function(n,video){
|
||||
try{
|
||||
video.play()
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
addOnTabAway('powerVideo', function () {
|
||||
powerVideoWindow.find('video').each(function(n,video){
|
||||
console.log(video)
|
||||
try{
|
||||
video.pause()
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
})
|
||||
// addOnTabReopen('powerVideo', function () {
|
||||
// drawMonitorsList()
|
||||
// })
|
||||
|
|
|
@ -11,25 +11,28 @@ $(document).ready(function(){
|
|||
}
|
||||
}
|
||||
function drawDaysToList(videos,toBegin,frames){
|
||||
var videosSortedByDays = sortVideosByDays(videos)
|
||||
var framesSortedByDays = sortFramesByDays(frames)
|
||||
$.each(videosSortedByDays,function(monitorId,days){
|
||||
if(!framesSortedByDays[monitorId])framesSortedByDays[monitorId] = {};
|
||||
$.each(days,function(dayKey,videos){
|
||||
var copyOfVideos = ([]).concat(videos).reverse()
|
||||
var listOfDays = getAllDays(videos,frames)
|
||||
var videosSortedByDays = Object.assign({},listOfDays,sortVideosByDays(videos))
|
||||
var framesSortedByDays = Object.assign({},listOfDays,sortFramesByDays(frames))
|
||||
$.each(listOfDays,function(monitorId,days){
|
||||
$.each(days,function(dayKey){
|
||||
var copyOfVideos = ([]).concat(videosSortedByDays[monitorId][dayKey] || []).reverse()
|
||||
var copyOfFrames = ([]).concat(framesSortedByDays[monitorId][dayKey] || []).reverse()
|
||||
theList.append(createDayCard(copyOfVideos,dayKey,monitorId))
|
||||
theList.append(createDayCard(copyOfVideos,copyOfFrames,dayKey,monitorId))
|
||||
var theChildren = theList.children()
|
||||
var createdCardCarrier = toBegin ? theChildren.first() : theChildren.last()
|
||||
bindFrameFindingByMouseMoveForDay(createdCardCarrier,dayKey,copyOfVideos,copyOfFrames)
|
||||
// preloadAllTimelapseFramesToMemoryFromVideoList(framesSortedByDays)
|
||||
// preloadAllTimelapseFramesToMemoryFromVideoList(copyOfFrames)
|
||||
})
|
||||
})
|
||||
}
|
||||
function drawListFiller(filler){
|
||||
theList.html(`<div class="text-center ${definitions.Theme.isDark ? 'text-white' : ''} pt-4"><h3>${filler}</h3></div>`);
|
||||
}
|
||||
function loadVideos(options,callback){
|
||||
theList.empty();
|
||||
drawListFiller(`<i class="fa fa-spinner fa-pulse"></i>`)
|
||||
var currentDate = new Date()
|
||||
var videoRange = parseInt(videoRangeEl.val()) || 72
|
||||
var videoRange = parseInt(videoRangeEl.val()) || 1
|
||||
options.videoRange = videoRange
|
||||
options.startDate = moment(currentDate).subtract(videoRange, 'hours')._d;
|
||||
options.endDate = moment(currentDate)._d;
|
||||
|
@ -46,11 +49,9 @@ $(document).ready(function(){
|
|||
callback(data)
|
||||
}
|
||||
getVideos(options,function(data){
|
||||
if(data.videos.length === 0){
|
||||
options.limit = 20
|
||||
delete(options.startDate)
|
||||
delete(options.endDate)
|
||||
getVideos(options,drawVideoData)
|
||||
theList.empty()
|
||||
if(data.videos.length === 0 && data.frames.length === 0){
|
||||
drawListFiller(lang['No Data'])
|
||||
}else{
|
||||
drawVideoData(data)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ $(document).ready(function(e){
|
|||
var regionEditorCanvasHolder = regionEditorWindow.find('.canvas_holder')
|
||||
var regionEditorMonitorsList = $('#region_editor_monitors')
|
||||
var regionEditorLiveView = $('#region_editor_live')
|
||||
var regionEditorGridOverlay = regionEditorWindow.find('.grid')
|
||||
var useRegionStillImage = false
|
||||
var getRegionEditorCanvas = function(){
|
||||
return regionEditorWindow.find('canvas')
|
||||
|
@ -113,25 +114,44 @@ $(document).ready(function(e){
|
|||
}
|
||||
function initLiveStream(monitorId){
|
||||
var monitorId = monitorId || getCurrentlySelectedMonitorId()
|
||||
var apiPoint = 'embed'
|
||||
var liveElement = regionEditorLiveView.find('iframe,img')
|
||||
regionEditorLiveView.find('iframe,img').attr('src','').hide()
|
||||
if(useRegionStillImage){
|
||||
apiPoint = 'jpeg'
|
||||
}else{
|
||||
apiPoint = 'embed'
|
||||
}
|
||||
var apiUrl = `${getApiPrefix(apiPoint)}/${monitorId}`
|
||||
if(apiPoint === 'embed'){
|
||||
apiUrl += '/fullscreen|jquery|relative'
|
||||
}else{
|
||||
apiUrl += '/s.jpg'
|
||||
}
|
||||
if(liveElement.attr('src') !== apiUrl){
|
||||
var loadedMonitor = loadedMonitors[monitorId]
|
||||
var monitorDetails = loadedMonitor.details
|
||||
if(loadedMonitor.mode === 'stop'){
|
||||
var apiUrl = placeholder.getData(placeholder.plcimg({
|
||||
bgcolor: '#000',
|
||||
text: lang[`Cannot watch a monitor that isn't running.`],
|
||||
size: regionViewerDetails.detector_scale_x+'x'+regionViewerDetails.detector_scale_y
|
||||
}))
|
||||
liveElement.attr('src',apiUrl).show()
|
||||
}else{
|
||||
var apiPoint = 'embed'
|
||||
regionEditorLiveView.find('iframe,img').attr('src','').hide()
|
||||
regionEditorGridOverlay.css('background-size',`71px 71px`).show()
|
||||
if(monitorDetails.detector_motion_tile_mode === '1'){
|
||||
var tileSize = monitorDetails.detector_tile_size || 20
|
||||
regionEditorGridOverlay.css('background-size',`${tileSize}px ${tileSize}px`).show()
|
||||
}else{
|
||||
regionEditorGridOverlay.hide()
|
||||
}
|
||||
if(useRegionStillImage){
|
||||
apiPoint = 'jpeg'
|
||||
}else{
|
||||
apiPoint = 'embed'
|
||||
}
|
||||
var apiUrl = `${getApiPrefix(apiPoint)}/${monitorId}`
|
||||
if(apiPoint === 'embed'){
|
||||
apiUrl += '/fullscreen|jquery|relative'
|
||||
}else{
|
||||
apiUrl += '/s.jpg'
|
||||
}
|
||||
if(liveElement.attr('src') !== apiUrl){
|
||||
liveElement.attr('src',apiUrl).show()
|
||||
}
|
||||
liveElement.attr('width',regionViewerDetails.detector_scale_x)
|
||||
liveElement.attr('height',regionViewerDetails.detector_scale_y)
|
||||
}
|
||||
liveElement.attr('width',regionViewerDetails.detector_scale_x)
|
||||
liveElement.attr('height',regionViewerDetails.detector_scale_y)
|
||||
|
||||
}
|
||||
var initCanvas = function(dontReloadStream){
|
||||
var newArray = [];
|
||||
|
@ -302,5 +322,8 @@ $(document).ready(function(e){
|
|||
drawMonitorListToSelector(regionEditorMonitorsList)
|
||||
regionEditorMonitorsList.val(theSelected)
|
||||
})
|
||||
addOnTabAway('regionEditor', function () {
|
||||
regionEditorLiveView.find('iframe,img').remove()
|
||||
})
|
||||
drawSubMenuItems('regionEditor',definitions['Region Editor'])
|
||||
})
|
||||
|
|
|
@ -12,7 +12,6 @@ function buildTabHtml(tabName,tabLabel,tabIcon){
|
|||
<i class="fa fa-${tabIcon ? tabIcon : 'file-o'}"></i> ${tabLabel} <span class="delete-tab align-text-bottom"><i class="fa fa-times"></i></span>
|
||||
</a>
|
||||
</li>`
|
||||
return `<a class="nav-link" page-open="${tabName}">${tabLabel} <span class="delete-tab badge btn bg-light text-dark rounded-pill align-text-bottom"><i class="fa fa-times"></i></span></a>`
|
||||
}
|
||||
function drawMonitorIconToMenu(item){
|
||||
var html = `<li class="nav-item monitor-icon monitor_block glM${item.mid}" data-ke="${item.ke}" data-mid="${item.mid}" data-status-code="${item.code}">
|
||||
|
|
|
@ -95,7 +95,7 @@ function drawAddStorageIndicators(){
|
|||
var el = $(`#indicator-${storage.name}`)
|
||||
loadedIndicators[storage.name] = {
|
||||
value: el.find('.value'),
|
||||
percent: el.find('.percent'),
|
||||
percent: el.find('.indicator-percent'),
|
||||
progressBar: el.find('.progress-bar'),
|
||||
}
|
||||
})
|
||||
|
@ -143,6 +143,9 @@ $('body')
|
|||
}
|
||||
_this.attr('type',type)
|
||||
})
|
||||
function parseDiskUsePercent(diskUsed,diskLimit){
|
||||
return parseFloat((diskUsed/diskLimit)*100).toFixed(1)+'%'
|
||||
}
|
||||
onWebSocketEvent(function (d){
|
||||
switch(d.f){
|
||||
case'init_success':
|
||||
|
@ -173,7 +176,10 @@ onWebSocketEvent(function (d){
|
|||
case'diskUsed':
|
||||
var diskLimit = d.limit || 10000
|
||||
var diskUsed = d.size
|
||||
var percent = parseInt((diskUsed/diskLimit)*100)+'%';
|
||||
var percent = parseDiskUsePercent(diskUsed,diskLimit);
|
||||
var videosPercent = parseDiskUsePercent(d.usedSpaceVideos,diskLimit);
|
||||
var timelapsePercent = parseDiskUsePercent(d.usedSpaceTimelapseFrames,diskLimit);
|
||||
var fileBinPercent = parseDiskUsePercent(d.usedSpaceFilebin,diskLimit);
|
||||
var humanText = parseFloat(diskUsed)
|
||||
if(humanText > 1000){
|
||||
humanText = (humanText / 1000).toFixed(2) + ' GB'
|
||||
|
@ -182,7 +188,12 @@ onWebSocketEvent(function (d){
|
|||
}
|
||||
diskIndicatorBarUsed.html(humanText)
|
||||
diskIndicatorPercentText.html(percent)
|
||||
diskIndicatorBar.css('width',percent)
|
||||
diskIndicatorBar[0].style.width = videosPercent
|
||||
diskIndicatorBar[0].title = `${lang['Video Share']} : ${videosPercent}`
|
||||
diskIndicatorBar[1].style.width = timelapsePercent
|
||||
diskIndicatorBar[1].title = `${lang['Timelapse Frames Share']} : ${timelapsePercent}`
|
||||
diskIndicatorBar[2].style.width = fileBinPercent
|
||||
diskIndicatorBar[2].title = `${lang['FileBin Share']} : ${fileBinPercent}`
|
||||
if(d.addStorage){
|
||||
$.each(d.addStorage,function(n,storage){
|
||||
var percent = parseInt((storage.usedSpace/storage.sizeLimit)*100)+'%'
|
||||
|
|
|
@ -209,13 +209,18 @@ $(document).ready(function(){
|
|||
permissionsSection.find('.permission-view select').each(function(n,v){
|
||||
var el = $(v)
|
||||
var monitorId = el.attr('monitor')
|
||||
var value = el.val()
|
||||
var value = el.val() // permissions selected
|
||||
$.each(value,function(n,permissionNameSelected){
|
||||
if(!foundSelected[permissionNameSelected])foundSelected[permissionNameSelected] = []
|
||||
foundSelected[permissionNameSelected].push(monitorId)
|
||||
})
|
||||
})
|
||||
details = Object.assign(details,foundSelected)
|
||||
details = Object.assign(details,{
|
||||
'monitors': [],
|
||||
'monitor_edit': [],
|
||||
'video_view': [],
|
||||
'video_delete': [],
|
||||
},foundSelected)
|
||||
detailsElement.val(JSON.stringify(details))
|
||||
}
|
||||
var getCompleteForm = function(){
|
||||
|
|
|
@ -1,3 +1,25 @@
|
|||
var apiBaseUrl = getApiPrefix()
|
||||
function getTimelapseFrames(monitorId,startDate,endDate,limit){
|
||||
return new Promise((resolve,reject) => {
|
||||
if(!monitorId || !startDate || !endDate){
|
||||
console.log(new Error(`getTimelapseFrames error : Failed to get proper params`))
|
||||
resolve([])
|
||||
return
|
||||
}
|
||||
var queryString = [
|
||||
'start=' + startDate,
|
||||
'end=' + endDate,
|
||||
limit === 'noLimit' ? `noLimit=1` : limit ? `limit=${limit}` : `limit=50`
|
||||
]
|
||||
var apiURL = apiBaseUrl + '/timelapse/' + $user.ke + '/' + monitorId
|
||||
$.getJSON(apiURL + '?' + queryString.join('&'),function(data){
|
||||
$.each(data,function(n,fileInfo){
|
||||
fileInfo.href = apiURL + '/' + fileInfo.filename.split('T')[0] + '/' + fileInfo.filename
|
||||
})
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
$(document).ready(function(e){
|
||||
//Timelapse JPEG Window
|
||||
var timelapseWindow = $('#tab-timelapseViewer')
|
||||
|
@ -14,7 +36,7 @@ $(document).ready(function(e){
|
|||
var liveStreamView = timelapseWindow.find('.liveStreamView')
|
||||
var monitorsList = timelapseWindow.find('.monitors_list')
|
||||
var downloadButton = timelapseWindow.find('.download_mp4')
|
||||
var apiBaseUrl = getApiPrefix()
|
||||
var selectAllBox = timelapseWindow.find('.select-all')
|
||||
var downloadRecheckTimers = {}
|
||||
var currentPlaylist = {}
|
||||
var frameSelected = null
|
||||
|
@ -64,29 +86,22 @@ $(document).ready(function(e){
|
|||
liveStreamView.find('iframe').width(playBackViewImage.width())
|
||||
|
||||
}
|
||||
var drawTimelapseWindowElements = function(selectedMonitor,startDate,endDate){
|
||||
function drawTimelapseWindowElements(selectedMonitor,startDate,endDate){
|
||||
setDownloadButtonLabel(lang['Build Video'], 'database')
|
||||
var dateRange = getSelectedTime(false)
|
||||
if(!startDate)startDate = dateRange.startDate
|
||||
if(!endDate)endDate = dateRange.endDate
|
||||
if(!selectedMonitor)selectedMonitor = monitorsList.val()
|
||||
var queryString = [
|
||||
'start=' + startDate,
|
||||
'end=' + endDate,
|
||||
'noLimit=1'
|
||||
]
|
||||
var frameIconsHtml = ''
|
||||
var apiURL = apiBaseUrl + '/timelapse/' + $user.ke + '/' + selectedMonitor
|
||||
$.getJSON(apiURL + '?' + queryString.join('&'),function(data){
|
||||
getTimelapseFrames(selectedMonitor,startDate,endDate,'noLimit').then((data) => {
|
||||
if(data && data[0]){
|
||||
var firstFilename = data[0].filename
|
||||
frameSelected = firstFilename
|
||||
currentPlaylist = {}
|
||||
currentPlaylistArray = []
|
||||
$.each(data.reverse(),function(n,fileInfo){
|
||||
fileInfo.href = apiURL + '/' + fileInfo.filename.split('T')[0] + '/' + fileInfo.filename
|
||||
fileInfo.number = n
|
||||
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>'
|
||||
frameIconsHtml += '<div class="col-md-4 frame-container" frame-container="' + fileInfo.filename + '"><div class="frame" data-filename="' + fileInfo.filename + '" frame-container-unloaded="' + fileInfo.href + '"><div class="button-strip"><input name="' + fileInfo.href + '" value="' + fileInfo.filename + '" type="checkbox" class="form-check-input"><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
|
||||
})
|
||||
currentPlaylistArray = data
|
||||
|
@ -96,7 +111,7 @@ $(document).ready(function(e){
|
|||
resetFilmStripPositions()
|
||||
loadVisibleTimelapseFrames()
|
||||
}else{
|
||||
frameIconsHtml = lang['No Data']
|
||||
frameIconsHtml = `<div class="text-center">${lang['No Data']}</div>`
|
||||
frameIcons.html(frameIconsHtml)
|
||||
}
|
||||
})
|
||||
|
@ -137,7 +152,7 @@ $(document).ready(function(e){
|
|||
var playTimelapse = function(){
|
||||
var playPauseText = timelapseWindow.find('.playPauseText')
|
||||
canPlay = true
|
||||
playPauseText.text(lang.Pause)
|
||||
playPauseText.html(`<i class="fa fa-pause"></i> ${lang.Pause}`)
|
||||
startPlayLoop()
|
||||
}
|
||||
var destroyTimelapse = function(){
|
||||
|
@ -151,7 +166,7 @@ $(document).ready(function(e){
|
|||
var pauseTimelapse = function(){
|
||||
var playPauseText = timelapseWindow.find('.playPauseText')
|
||||
canPlay = false
|
||||
playPauseText.text(lang.Play)
|
||||
playPauseText.html(`<i class="fa fa-play"></i> ${lang.Play}`)
|
||||
clearTimeout(playIntervalTimer)
|
||||
playIntervalTimer = null
|
||||
}
|
||||
|
@ -169,6 +184,47 @@ $(document).ready(function(e){
|
|||
var setDownloadButtonLabel = function(text,icon){
|
||||
downloadButton.html(icon ? iconHtml(icon) + text : text)
|
||||
}
|
||||
function deleteFrame(frame){
|
||||
return new Promise((resolve,reject) => {
|
||||
$.getJSON((frame.href || frame) + '/delete',function(response){
|
||||
resolve(response)
|
||||
})
|
||||
})
|
||||
}
|
||||
async function deleteFrames(frameHrefs){
|
||||
for (let i = 0; i < frameHrefs.length; i++) {
|
||||
const frameHref = frameHrefs[i]
|
||||
await deleteFrame(frameHref)
|
||||
}
|
||||
}
|
||||
function deleteSelectedFrames(){
|
||||
var checkedBoxes = frameIcons.serializeObject()
|
||||
var frameHrefs = Object.keys(checkedBoxes)
|
||||
var fileNames = Object.values(checkedBoxes)
|
||||
$.confirm.create({
|
||||
title: lang['Delete selected'],
|
||||
body: lang.DeleteTheseMsg + `<br><br><img style="max-width:100%" src="${frameHrefs[0]}">`,
|
||||
clickOptions: {
|
||||
class: 'btn-danger',
|
||||
title: lang.Delete,
|
||||
},
|
||||
clickCallback: function(){
|
||||
deleteFrames(frameHrefs)
|
||||
fileNames.forEach((filename) => {
|
||||
frameIcons.find(`[frame-container="${filename}"]`).remove()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
function toggleSelectOnAllFrames(){
|
||||
var isMasterToggleSelected = selectAllBox.is(':checked')
|
||||
var checkBoxes = frameIcons.find('input[type="checkbox"]')
|
||||
if(isMasterToggleSelected){
|
||||
checkBoxes.prop('checked',true)
|
||||
}else{
|
||||
checkBoxes.prop('checked',false)
|
||||
}
|
||||
}
|
||||
timelapseWindow.on('click','.frame',function(){
|
||||
pauseTimelapse()
|
||||
var selectedFrame = $(this).attr('data-filename')
|
||||
|
@ -196,39 +252,46 @@ $(document).ready(function(e){
|
|||
class: 'btn-danger',
|
||||
title: lang.Delete,
|
||||
},
|
||||
clickCallback: function(){
|
||||
$.getJSON(frame.href + '/delete',function(response){
|
||||
if(response.ok){
|
||||
el.parent().remove()
|
||||
}
|
||||
})
|
||||
clickCallback: async function(){
|
||||
const response = await deleteFrame(frame)
|
||||
if(response.ok){
|
||||
el.parent().remove()
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
timelapseWindow.on('click','.delete-selected',function(e){
|
||||
deleteSelectedFrames()
|
||||
})
|
||||
selectAllBox.click(function(e){
|
||||
toggleSelectOnAllFrames()
|
||||
})
|
||||
timelapseWindow.on('click','.frame input',function(e){
|
||||
e.stopPropagation()
|
||||
const checked = $(this).is(':checked')
|
||||
if(!checked){
|
||||
selectAllBox.prop('checked',false)
|
||||
}
|
||||
})
|
||||
downloadButton.click(function(){
|
||||
var fps = fpsSelector.val()
|
||||
var dateRange = getSelectedTime(false)
|
||||
var startDate = dateRange.startDate
|
||||
var endDate = dateRange.endDate
|
||||
var selectedMonitor = monitorsList.val()
|
||||
var parsedFrames = JSON.stringify(currentPlaylistArray.map(function(frame){
|
||||
window.askedForTimelapseVideoBuild = true
|
||||
var parsedFrames = currentPlaylistArray.map(function(frame){
|
||||
return {
|
||||
mid: frame.mid,
|
||||
ke: frame.ke,
|
||||
filename: frame.filename,
|
||||
}
|
||||
}));
|
||||
$.post(apiBaseUrl + '/timelapseBuildVideo/' + $user.ke + '/' + selectedMonitor,{
|
||||
fps: fps,
|
||||
});
|
||||
mainSocket.f({
|
||||
f: 'timelapseVideoBuild',
|
||||
mid: selectedMonitor,
|
||||
frames: parsedFrames,
|
||||
},function(response){
|
||||
setDownloadButtonLabel(response.msg, '')
|
||||
new PNotify({
|
||||
title: lang['Timelapse Frames Video'],
|
||||
text: response.msg,
|
||||
type: response.fileExists ? 'success' : 'info'
|
||||
})
|
||||
if(response.fileExists)downloadTimelapseVideo(response);
|
||||
fps: fps,
|
||||
})
|
||||
})
|
||||
function isElementVisible (el) {
|
||||
|
@ -254,10 +317,7 @@ $(document).ready(function(e){
|
|||
}
|
||||
function downloadTimelapseVideo(data){
|
||||
var downloadUrl = buildFileBinUrl(data)
|
||||
var a = document.createElement('a')
|
||||
a.href = downloadUrl
|
||||
a.download = data.name
|
||||
a.click()
|
||||
downloadFile(downloadUrl,data.name)
|
||||
}
|
||||
function onTimelapseVideoBuildComplete(data){
|
||||
var saveBuiltVideo = dashboardOptions().switches.timelapseSaveBuiltVideo
|
||||
|
@ -303,6 +363,18 @@ $(document).ready(function(e){
|
|||
}
|
||||
onWebSocketEvent(function(data){
|
||||
switch(data.f){
|
||||
case'timelapse_build_requested':
|
||||
console.log(data)
|
||||
var response = data.buildResponse;
|
||||
setDownloadButtonLabel(response.msg, '')
|
||||
new PNotify({
|
||||
title: lang['Timelapse Frames Video'],
|
||||
text: response.msg,
|
||||
type: response.fileExists ? 'success' : 'info'
|
||||
});
|
||||
if(response.fileExists && window.askedForTimelapseVideoBuild)downloadTimelapseVideo(response);
|
||||
window.askedForTimelapseVideoBuild = false
|
||||
break;
|
||||
case'fileBin_item_added':
|
||||
var saveBuiltVideo = dashboardOptions().switches.timelapseSaveBuiltVideo
|
||||
let statusText = `${lang['Done!']}`
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
function getVideoPlayerTabId(video){
|
||||
return `videoPlayer-${video.mid}-${moment(video.time).format('YYYY-MM-DD-HH-mm-ss')}`
|
||||
}
|
||||
$(document).ready(function(){
|
||||
var theBlock = $('#tab-videoPlayer')
|
||||
window.createVideoPlayerTab = function(video,timeStart){
|
||||
window.getVideoPlayerTabId = function(video){
|
||||
return `videoPlayer-${video.mid}-${moment(video.time).format('YYYY-MM-DD-HH-mm-ss')}`
|
||||
}
|
||||
window.createVideoPlayerTab = function(video){
|
||||
var newTabId = getVideoPlayerTabId(video)
|
||||
var humanStartTime = formattedTime(video.time,true)
|
||||
var humanEndTime = formattedTime(video.end,true)
|
||||
var tabLabel = `<b>${lang['Video']}</b> : ${loadedMonitors[video.mid].name} : ${formattedTime(video.time,true)}`
|
||||
var videoUrl = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
var videoUrl = getLocation() + video.href
|
||||
var hasRows = video.events && video.events.length > 0
|
||||
var loadedEvents = {}
|
||||
var eventMatrixHtml = ``
|
||||
if(hasRows){
|
||||
var objectsFound = {}
|
||||
eventMatrixHtml += `<div class="border-top-dotted border-top-dark pt-2 mt-2"><h3>${lang.Events}</h3></div>`
|
||||
$.each(video.events,function(n,theEvent){
|
||||
loadedEvents[new Date(theEvent.time)] = theEvent
|
||||
var objectsFound = {}
|
||||
|
@ -35,46 +34,46 @@ $(document).ready(function(){
|
|||
})
|
||||
eventMatrixHtml += `</div>`
|
||||
}
|
||||
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="mt-3 ${definitions.Theme.isDark ? 'bg-dark text-white' : 'bg-light text-dark'} rounded shadow-sm" style="overflow:hidden">
|
||||
<div class="d-flex flex-row">
|
||||
<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>
|
||||
var baseHtml = `<main class="container page-tab tab-videoPlayer" id="tab-${newTabId}" video-id="${video.mid}${video.time}" data-time="${video.time}" data-mid="${video.mid}">
|
||||
<div class="my-3 ${definitions.Theme.isDark ? 'bg-dark text-white' : 'bg-light text-dark'} rounded shadow-sm">
|
||||
<div class="p-3">
|
||||
<h6 class="video-title border-bottom-dotted border-bottom-dark pb-2 mb-0">${tabLabel}</h6>
|
||||
</div>
|
||||
<div style="position: relative">
|
||||
<div class="tab-videoPlayer-view-container" style="position: relative">
|
||||
<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>
|
||||
</div>
|
||||
<div class="d-flex flex-row">
|
||||
<div class="d-block p-3">
|
||||
<b class="flex-grow-1">${lang.Started}</b>
|
||||
<div class="video-time">${humanStartTime}</div>
|
||||
</div>
|
||||
<div class="d-block p-3">
|
||||
<b class="flex-grow-1">${lang.Ended}</b>
|
||||
<div class="video-end">${humanEndTime}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
${eventMatrixHtml}
|
||||
</div>
|
||||
<div class="d-flex flex-row">
|
||||
<div class="flex-grow-1 bg-gradient-blue">
|
||||
<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>
|
||||
</div>
|
||||
<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 class="p-3">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<b class="flex-grow-1">${lang.Started}</b>
|
||||
<div class="video-time">${humanStartTime}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<b class="flex-grow-1">${lang.Ended}</b>
|
||||
<div class="video-end">${humanEndTime}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-block pt-4">
|
||||
<div class="btn-group btn-group-justified">
|
||||
<a class="btn btn-sm btn-success" download href="${videoUrl}"><i class="fa fa-download"></i> ${lang.Download}</a>
|
||||
${permissionCheck('video_delete',video.mid) ? `<a class="btn btn-sm btn-danger delete-video"><i class="fa fa-trash-o"></i> ${lang.Delete}</a>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
${eventMatrixHtml}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>`
|
||||
var tabCreateResponse = createNewTab(newTabId,tabLabel,baseHtml,{},null,'videoPlayer')
|
||||
var videoElement = tabCreateResponse.theTab.find('.tab-videoPlayer-video-element')[0]
|
||||
console.log(tabCreateResponse)
|
||||
if(!tabCreateResponse.existAlready){
|
||||
var videoElement = tabCreateResponse.theTab.find('.tab-videoPlayer-video-element')[0]
|
||||
var videoObjectContainer = tabCreateResponse.theTab.find('.tab-videoPlayer-event-objects')
|
||||
var videoHeight = videoObjectContainer.height()
|
||||
var videoWidth = videoObjectContainer.width()
|
||||
|
@ -92,7 +91,6 @@ $(document).ready(function(){
|
|||
}
|
||||
}
|
||||
}
|
||||
if(timeStart)videoElement.currentTime = timeStart;
|
||||
}
|
||||
window.closeVideoPlayer = function(tabId){
|
||||
console.log('closeVideoPlayer')
|
||||
|
@ -155,5 +153,6 @@ $(document).ready(function(){
|
|||
var newVideoTimeIndex = timeIndex - videoTime
|
||||
console.log(newVideoTimeIndex)
|
||||
videoEl[0].currentTime = newVideoTimeIndex
|
||||
videoEl[0].play()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
var loadedVideosInMemory = {}
|
||||
var loadedFramesMemory = {}
|
||||
var loadedFramesMemoryTimeout = {}
|
||||
var loadedFramesLock = {}
|
||||
function getLocalTimelapseImageLink(imageUrl){
|
||||
if(loadedFramesMemory[imageUrl]){
|
||||
if(loadedFramesLock[imageUrl]){
|
||||
return null;
|
||||
}else if(loadedFramesMemory[imageUrl]){
|
||||
return loadedFramesMemory[imageUrl]
|
||||
}else{
|
||||
loadedFramesLock[imageUrl] = true
|
||||
return new Promise((resolve,reject) => {
|
||||
fetch(imageUrl)
|
||||
.then(res => res.blob()) // Gets the response and returns it as a blob
|
||||
|
@ -16,8 +20,11 @@ function getLocalTimelapseImageLink(imageUrl){
|
|||
URL.revokeObjectURL(objectURL)
|
||||
delete(loadedFramesMemory[imageUrl])
|
||||
delete(loadedFramesMemoryTimeout[imageUrl])
|
||||
},1000 * 60 * 10)
|
||||
resolve(objectURL)
|
||||
},1000 * 60 * 10);
|
||||
loadedFramesLock[imageUrl] = false;
|
||||
resolve(objectURL);
|
||||
}).catch((err) => {
|
||||
resolve()
|
||||
});
|
||||
})
|
||||
}
|
||||
|
@ -38,7 +45,7 @@ async function preloadAllTimelapseFramesToMemoryFromVideoList(framesSortedByDays
|
|||
console.log ('Loaded! ',frame.href)
|
||||
}
|
||||
}
|
||||
function createVideoLinks(video){
|
||||
function createVideoLinks(video,options){
|
||||
var details = safeJsonParse(video.details)
|
||||
var queryString = []
|
||||
// if(details.isUTC === true){
|
||||
|
@ -102,8 +109,8 @@ function applyTimelapseFramesListToVideos(videos,events,keyName,reverseList){
|
|||
return newVideos
|
||||
}
|
||||
function getFrameOnVideoRow(percentageInward,video){
|
||||
var startTime = new Date(video.time)
|
||||
var endTime = new Date(video.end)
|
||||
var startTime = video.time
|
||||
var endTime = video.end
|
||||
var timeDifference = endTime - startTime
|
||||
var timeInward = timeDifference / (100 / percentageInward)
|
||||
var timeAdded = new Date(startTime.getTime() + timeInward) // ms
|
||||
|
@ -117,79 +124,79 @@ function getFrameOnVideoRow(percentageInward,video){
|
|||
timeAdded: timeAdded,
|
||||
}
|
||||
}
|
||||
function getVideoFromDay(percentageInward,videos){
|
||||
var startTime = new Date(videos[0].time)
|
||||
var endTime = new Date(videos[videos.length - 1].end)
|
||||
function getVideoFromDay(percentageInward,reversedVideos,startTime,endTime){
|
||||
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)
|
||||
var foundVideoIndex = reversedVideos.findIndex(function(row){
|
||||
return new Date(timeAdded) >= new Date(row.time)
|
||||
});
|
||||
var foundVideo = reversedVideos[foundVideoIndex - 1] || reversedVideos[foundVideoIndex] || reversedVideos[0]
|
||||
return foundVideo
|
||||
}
|
||||
function bindFrameFindingByMouseMove(createdCardCarrier,video){
|
||||
var createdCardElement = createdCardCarrier.find('.video-time-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(theEvent.details.matrices,function(n,matrix){
|
||||
if(!objectsFound[matrix.tag])objectsFound[matrix.tag] = 1
|
||||
++objectsFound[matrix.tag]
|
||||
})
|
||||
})
|
||||
$.each(objectsFound,function(tag,count){
|
||||
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.remove()
|
||||
}
|
||||
}
|
||||
// function bindFrameFindingByMouseMove(createdCardCarrier,video){
|
||||
// var createdCardElement = createdCardCarrier.find('.video-time-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(theEvent.details.matrices,function(n,matrix){
|
||||
// if(!objectsFound[matrix.tag])objectsFound[matrix.tag] = 1
|
||||
// ++objectsFound[matrix.tag]
|
||||
// })
|
||||
// })
|
||||
// $.each(objectsFound,function(tag,count){
|
||||
// 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.remove()
|
||||
// }
|
||||
// }
|
||||
function bindFrameFindingByMouseMoveForDay(createdCardCarrier,dayKey,videos,allFrames){
|
||||
var stripTimes = getStripStartAndEnd(videos,allFrames)
|
||||
var dayStart = stripTimes.start
|
||||
var dayEnd = stripTimes.end
|
||||
var createdCardElement = createdCardCarrier.find('.video-time-card')
|
||||
var timeImg = createdCardElement.find('.video-time-img')
|
||||
var rowHeader = createdCardElement.find('.video-time-header')
|
||||
var timeStrip = createdCardElement.find('.video-time-strip')
|
||||
var timeNeedleSeeker = createdCardElement.find('.video-time-needle-seeker')
|
||||
var dayStart = videos[0].time
|
||||
var dayEnd = videos[videos.length - 1].end
|
||||
var firstFrameOfDay = null
|
||||
var firstFrameOfDay = allFrames[0] || null
|
||||
$.each(videos,function(day,video){
|
||||
$.each(video.timelapseFrames,function(day,frame){
|
||||
if(!firstFrameOfDay)firstFrameOfDay = frame;
|
||||
|
@ -203,15 +210,16 @@ function bindFrameFindingByMouseMoveForDay(createdCardCarrier,dayKey,videos,allF
|
|||
}
|
||||
var videoSlices = createdCardElement.find('.video-day-slice')
|
||||
var videoTimeLabel = createdCardElement.find('.video-time-label')
|
||||
var currentlySelected = null
|
||||
var currentlySelected = videos[0]
|
||||
var currentlySelectedFrame = null
|
||||
var reversedVideos = ([]).concat(videos).reverse();
|
||||
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)
|
||||
var videoFound = videos[0] ? getVideoFromDay(percentMoved,reversedVideos,dayStart,dayEnd) : null
|
||||
createdCardElement.find(`[data-time]`).css('background-color','')
|
||||
if(videoFound){
|
||||
// var videoSlice = createdCardElement.find(`[data-time="${videoFound.time}"]`).css('background-color','rgba(255,255,255,0.3)')
|
||||
|
@ -228,8 +236,8 @@ function bindFrameFindingByMouseMoveForDay(createdCardCarrier,dayKey,videos,allF
|
|||
}
|
||||
// draw frame
|
||||
var result = getFrameOnVideoRow(percentMoved,{
|
||||
time: videos[0].time,
|
||||
end: videos[videos.length - 1].end,
|
||||
time: dayStart,
|
||||
end: dayEnd,
|
||||
timelapseFrames: allFrames,
|
||||
})
|
||||
var frameFound = result.foundFrame
|
||||
|
@ -238,7 +246,7 @@ function bindFrameFindingByMouseMoveForDay(createdCardCarrier,dayKey,videos,allF
|
|||
currentlySelectedFrame = Object.assign({},frameFound)
|
||||
setTimeout(async function(){
|
||||
var frameUrl = await getLocalTimelapseImageLink(frameFound.href)
|
||||
if(currentlySelectedFrame.time === frameFound.time)timeImg.attr('src',frameUrl);
|
||||
if(frameUrl && currentlySelectedFrame.time === frameFound.time)timeImg.attr('src',frameUrl);
|
||||
},1)
|
||||
}
|
||||
timeNeedleSeeker.attr('video-slice-seeked',result.timeInward).css('left',`${percentMoved}%`)
|
||||
|
@ -323,35 +331,65 @@ function sortFramesByDays(frames){
|
|||
frame.href = libURL + apiURL + '/' + frame.filename.split('T')[0] + '/' + frame.filename
|
||||
days[frame.mid][theDayKey].push(frame)
|
||||
})
|
||||
console.log(days)
|
||||
return days
|
||||
}
|
||||
function getVideoPercentWidthForDay(row,videos){
|
||||
function getAllDays(videos,frames){
|
||||
var listOfDays = {}
|
||||
$.each(loadedMonitors,function(monitorId){
|
||||
if(!listOfDays[monitorId])listOfDays[monitorId] = {}
|
||||
})
|
||||
videos.forEach(function(video){
|
||||
var videoTime = new Date(video.time)
|
||||
var theDayKey = `${videoTime.getDate()}-${videoTime.getMonth()}-${videoTime.getFullYear()}`
|
||||
listOfDays[video.mid][theDayKey] = []
|
||||
})
|
||||
frames.forEach(function(frame){
|
||||
var frameTime = new Date(frame.time)
|
||||
var theDayKey = `${frameTime.getDate()}-${frameTime.getMonth()}-${frameTime.getFullYear()}`
|
||||
listOfDays[frame.mid][theDayKey] = []
|
||||
})
|
||||
return listOfDays
|
||||
}
|
||||
function getStripStartAndEnd(videos,frames){
|
||||
var stripStartTimeByVideos = videos[0] ? new Date(videos[0].time) : null
|
||||
var stripEndTimeByVideos = videos[0] ? new Date(videos[videos.length - 1].end) : null
|
||||
var stripStartTimeByFrames = frames[0] ? new Date(frames[0].time) : stripStartTimeByVideos
|
||||
var stripEndTimeByFrames = frames[0] ? new Date(frames[frames.length - 1].time) : stripEndTimeByVideos
|
||||
var stripStartTime = stripStartTimeByVideos && stripStartTimeByVideos < stripStartTimeByFrames ? stripStartTimeByVideos : stripStartTimeByFrames
|
||||
var stripEndTime = stripEndTimeByVideos && stripEndTimeByVideos > stripEndTimeByFrames ? stripEndTimeByVideos : stripEndTimeByFrames
|
||||
return {
|
||||
start: new Date(stripStartTime),
|
||||
end: new Date(stripEndTime),
|
||||
}
|
||||
}
|
||||
function getVideoPercentWidthForDay(row,videos,frames){
|
||||
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 stripTimes = getStripStartAndEnd(videos,frames)
|
||||
var stripTimeDifference = stripTimes.end - stripTimes.start
|
||||
var percent = (timeDifference / stripTimeDifference) * 100
|
||||
return percent
|
||||
}
|
||||
function createDayCard(videos,dayKey,monitorId,classOverride){
|
||||
function createDayCard(videos,frames,dayKey,monitorId,classOverride){
|
||||
var html = ''
|
||||
var eventMatrixHtml = ``
|
||||
var dayParts = formattedTime(videos[0].time).split(' ')[1].split('-')
|
||||
var stripTimes = getStripStartAndEnd(videos,frames)
|
||||
var startTime = stripTimes.start
|
||||
var endTime = stripTimes.end
|
||||
var firstVideoTime = videos[0] ? videos[0].time : null
|
||||
var dayParts = formattedTime(startTime).split(' ')[1].split('-')
|
||||
var day = dayParts[2]
|
||||
var month = dayParts[1]
|
||||
var year = dayParts[0]
|
||||
$.each(videos,function(n,row){
|
||||
var nextRow = videos[n + 1]
|
||||
if(nextRow)console.log({time: row.end, end: nextRow.time},n + 1)
|
||||
var marginRight = !!nextRow ? getVideoPercentWidthForDay({time: row.end, end: nextRow.time},videos) : 0;
|
||||
eventMatrixHtml += `<div class="video-day-slice" data-mid="${row.mid}" data-time="${row.time}" style="width:${getVideoPercentWidthForDay(row,videos)}%;position:relative">`
|
||||
var marginRight = !!nextRow ? getVideoPercentWidthForDay({time: row.end, end: nextRow.time},videos,frames) : 0;
|
||||
eventMatrixHtml += `<div class="video-day-slice" data-mid="${row.mid}" data-time="${row.time}" style="width:${getVideoPercentWidthForDay(row,videos,frames)}%;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="margin-right:${leftPercent}%"></div>`
|
||||
eventMatrixHtml += `<div class="video-time-needle video-time-needle-event" style="margin-left:${leftPercent}%"></div>`
|
||||
})
|
||||
}
|
||||
eventMatrixHtml += `</div>`
|
||||
|
@ -365,7 +403,7 @@ function createDayCard(videos,dayKey,monitorId,classOverride){
|
|||
<div class="flex-grow-1 p-3">
|
||||
<b>${loadedMonitors[monitorId] ? loadedMonitors[monitorId].name : monitorId}</b>
|
||||
<div class="${definitions.Theme.isDark ? 'text-white' : ''}">
|
||||
<span class="video-time-label">${formattedTime(videos[0].time)} to ${formattedTime(videos[videos.length - 1].end)}</span>
|
||||
<span class="video-time-label">${formattedTime(startTime)} to ${formattedTime(endTime)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-right p-3" style="background:rgba(0,0,0,0.5)">
|
||||
|
@ -379,7 +417,7 @@ function createDayCard(videos,dayKey,monitorId,classOverride){
|
|||
</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 class="video-time-needle video-time-needle-seeker" ${firstVideoTime ? `video-time-seeked-video-position="${firstVideoTime}"` : ''} data-mid="${monitorId}" style="z-index: 2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>`
|
||||
|
@ -398,32 +436,40 @@ function loadVideoData(video){
|
|||
loadedVideosInMemory[`${video.mid}${video.time}`] = video
|
||||
}
|
||||
function getVideos(options,callback){
|
||||
options = options ? options : {}
|
||||
var requestQueries = []
|
||||
var monitorId = options.monitorId
|
||||
var limit = options.limit || 300
|
||||
var eventStartTime
|
||||
var eventEndTime
|
||||
// var startDate = options.startDate
|
||||
// var endDate = options.endDate
|
||||
if(options.startDate){
|
||||
eventStartTime = formattedTimeForFilename(options.startDate,false)
|
||||
requestQueries.push(`start=${eventStartTime}`)
|
||||
}
|
||||
if(options.endDate){
|
||||
eventEndTime = formattedTimeForFilename(options.endDate,false)
|
||||
requestQueries.push(`end=${eventEndTime}`)
|
||||
}
|
||||
$.getJSON(`${getApiPrefix(`videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(data){
|
||||
var videos = data.videos
|
||||
$.getJSON(`${getApiPrefix(`timelapse`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(timelapseFrames){
|
||||
$.getJSON(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`limit=${limit}`]).join('&')}`,function(eventData){
|
||||
var newVideos = applyDataListToVideos(videos,eventData)
|
||||
newVideos = applyTimelapseFramesListToVideos(newVideos,timelapseFrames,'timelapseFrames',true)
|
||||
$.each(newVideos,function(n,video){
|
||||
loadVideoData(video)
|
||||
return new Promise((resolve,reject) => {
|
||||
options = options ? options : {}
|
||||
var searchQuery = options.searchQuery
|
||||
var requestQueries = []
|
||||
var monitorId = options.monitorId
|
||||
var customVideoSet = options.customVideoSet
|
||||
var limit = options.limit || 300
|
||||
var eventStartTime
|
||||
var eventEndTime
|
||||
// var startDate = options.startDate
|
||||
// var endDate = options.endDate
|
||||
if(options.startDate){
|
||||
eventStartTime = formattedTimeForFilename(options.startDate,false)
|
||||
requestQueries.push(`start=${eventStartTime}`)
|
||||
}
|
||||
if(options.endDate){
|
||||
eventEndTime = formattedTimeForFilename(options.endDate,false)
|
||||
requestQueries.push(`end=${eventEndTime}`)
|
||||
}
|
||||
if(searchQuery){
|
||||
requestQueries.push(`search=${searchQuery}`)
|
||||
}
|
||||
$.getJSON(`${getApiPrefix(customVideoSet ? customVideoSet : searchQuery ? `videosByEventTag` : `videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(data){
|
||||
var videos = data.videos
|
||||
$.getJSON(`${getApiPrefix(`timelapse`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(timelapseFrames){
|
||||
$.getJSON(`${getApiPrefix(`events`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`limit=${limit}`]).join('&')}`,function(eventData){
|
||||
var newVideos = applyDataListToVideos(videos,eventData)
|
||||
newVideos = applyTimelapseFramesListToVideos(newVideos,timelapseFrames,'timelapseFrames',true)
|
||||
$.each(newVideos,function(n,video){
|
||||
loadVideoData(video)
|
||||
})
|
||||
if(callback)callback({videos: newVideos, frames: timelapseFrames});
|
||||
resolve({videos: newVideos, frames: timelapseFrames})
|
||||
})
|
||||
callback({videos: newVideos, frames: timelapseFrames})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -452,6 +498,31 @@ function getEvents(options,callback){
|
|||
callback(eventData)
|
||||
})
|
||||
}
|
||||
function deleteVideo(video,callback){
|
||||
return new Promise((resolve,reject) => {
|
||||
var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
$.getJSON(videoEndpoint + '/delete',function(data){
|
||||
if(callback)callback(data)
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
async function deleteVideos(videos){
|
||||
for (let i = 0; i < videos.length; i++) {
|
||||
var video = videos[i];
|
||||
await deleteVideo(video)
|
||||
}
|
||||
}
|
||||
function downloadVideo(video){
|
||||
var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
downloadFile(videoEndpoint,video.filename)
|
||||
}
|
||||
async function downloadVideos(videos){
|
||||
for (let i = 0; i < videos.length; i++) {
|
||||
var video = videos[i];
|
||||
await downloadVideo(video)
|
||||
}
|
||||
}
|
||||
onWebSocketEvent(function(d){
|
||||
switch(d.f){
|
||||
case'video_delete':
|
||||
|
@ -459,6 +530,9 @@ onWebSocketEvent(function(d){
|
|||
$('[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)
|
||||
if(tabTree.name === videoPlayerId){
|
||||
goBackOneTab()
|
||||
}
|
||||
deleteTab(videoPlayerId)
|
||||
// if($.powerVideoWindow.currentDataObject&&$.powerVideoWindow.currentDataObject[d.filename]){
|
||||
// delete($.timelapse.currentVideos[$.powerVideoWindow.currentDataObject[d.filename].position])
|
||||
|
@ -474,12 +548,14 @@ onWebSocketEvent(function(d){
|
|||
})
|
||||
$(document).ready(function(){
|
||||
$('body')
|
||||
.on('click','.open-video',function(){
|
||||
.on('click','.open-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}`]
|
||||
createVideoPlayerTab(video)
|
||||
return false;
|
||||
})
|
||||
.on('click','[video-time-seeked-video-position]',function(){
|
||||
var el = $(this)
|
||||
|
@ -490,7 +566,8 @@ $(document).ready(function(){
|
|||
timeInward = timeInward < 0 ? 0 : timeInward
|
||||
createVideoPlayerTab(video,timeInward)
|
||||
})
|
||||
.on('click','.delete-video',function(){
|
||||
.on('click','.delete-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
|
@ -510,10 +587,39 @@ $(document).ready(function(){
|
|||
if(data.ok){
|
||||
console.log('Video Deleted')
|
||||
}else{
|
||||
console.log('Video Not Deleted',data,deleteEndpoint)
|
||||
console.log('Video Not Deleted',data,videoEndpoint)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
});
|
||||
return false;
|
||||
})
|
||||
.on('click','.fix-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}`]
|
||||
var ext = video.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
$.confirm.create({
|
||||
title: lang["Fix Video"] + ' : ' + video.filename,
|
||||
body: `${lang.FixVideoMsg}<br><br><div class="row"><video class="video_video" autoplay loop controls><source src="${videoEndpoint}" type="video/${ext}"></video></div>`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-wrench"></i> ' + lang.Fix,
|
||||
class: 'btn-danger btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
$.getJSON(videoEndpoint + '/fix',function(data){
|
||||
if(data.ok){
|
||||
console.log('Video Fixed')
|
||||
}else{
|
||||
console.log('Video Not Fixed',data,videoEndpoint)
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return false;
|
||||
})
|
||||
})
|
||||
|
|
|
@ -4,7 +4,37 @@ $(document).ready(function(e){
|
|||
var dateSelector = theEnclosure.find('.date_selector')
|
||||
var videosTableDrawArea = $('#videosTable_draw_area')
|
||||
var videosTablePreviewArea = $('#videosTable_preview_area')
|
||||
var loadedVideosInMemory = {};
|
||||
var objectTagSearchField = $('#videosTable_tag_search')
|
||||
var cloudVideoCheckSwitch = $('#videosTable_cloudVideos')
|
||||
var loadedVideosTable = [];
|
||||
var redrawTimeout;
|
||||
var frameUrlCache = {}
|
||||
var frameUrlCacheTimeouts = {}
|
||||
async function getSnapshotFromVideoTimeFrame(monitorId,startDate,endDate){
|
||||
const frameUrlCacheId = `${monitorId}${startDate}${endDate}`
|
||||
if(frameUrlCache[frameUrlCacheId]){
|
||||
return frameUrlCache[frameUrlCacheId]
|
||||
}else{
|
||||
const frame = (await getTimelapseFrames(monitorId,startDate,endDate,1))[0]
|
||||
const href = frame && frame.href ? frame.href : ''
|
||||
frameUrlCache[frameUrlCacheId] = `${href}`
|
||||
frameUrlCacheTimeouts[frameUrlCacheId] = setTimeout(() => {
|
||||
delete(frameUrlCache[frameUrlCacheId])
|
||||
delete(frameUrlCacheTimeouts[frameUrlCacheId])
|
||||
},1000 * 60 * 15)
|
||||
return href
|
||||
}
|
||||
}
|
||||
function loadFramesForVideosInView(){
|
||||
videosTableDrawArea.find('.video-thumbnail').each(async (n,imgEl) => {
|
||||
const el = $(imgEl)
|
||||
const monitorId = el.attr('data-mid')
|
||||
const startDate = el.attr('data-time')
|
||||
const endDate = el.attr('data-end')
|
||||
const href = await getSnapshotFromVideoTimeFrame(monitorId,startDate,endDate)
|
||||
imgEl.innerHTML = href ? `<img class="pop-image cursor-pointer" src="${href}">` : ''
|
||||
})
|
||||
}
|
||||
function openVideosTableView(monitorId,startDate,endDate){
|
||||
drawVideosTableViewElements(monitorId,startDate,endDate)
|
||||
}
|
||||
|
@ -37,65 +67,137 @@ $(document).ready(function(e){
|
|||
monitorsList.change(function(){
|
||||
drawVideosTableViewElements()
|
||||
})
|
||||
function drawVideosTableViewElements(selectedMonitor,startDate,endDate){
|
||||
objectTagSearchField.change(function(){
|
||||
drawVideosTableViewElements()
|
||||
})
|
||||
cloudVideoCheckSwitch.change(function(){
|
||||
drawVideosTableViewElements()
|
||||
})
|
||||
async function drawVideosTableViewElements(usePreloadedData){
|
||||
var dateRange = getSelectedTime(false)
|
||||
if(!startDate)startDate = dateRange.startDate
|
||||
if(!endDate)endDate = dateRange.endDate
|
||||
if(!selectedMonitor)selectedMonitor = monitorsList.val()
|
||||
var queryString = ['start=' + startDate,'end=' + endDate,'limit=0']
|
||||
var searchQuery = objectTagSearchField.val() || null
|
||||
var startDate = dateRange.startDate
|
||||
var endDate = dateRange.endDate
|
||||
var monitorId = monitorsList.val()
|
||||
var frameIconsHtml = ''
|
||||
var apiURL = getApiPrefix('videos') + '/' + selectedMonitor;
|
||||
var videosTableData = []
|
||||
loadedVideosInMemory = {}
|
||||
$.getJSON(apiURL + '?' + queryString.join('&'),function(data){
|
||||
videosTableDrawArea.bootstrapTable('destroy')
|
||||
videosTableDrawArea.bootstrapTable({
|
||||
pagination: true,
|
||||
search: true,
|
||||
columns: [
|
||||
{
|
||||
field: 'monitorName',
|
||||
title: lang['Monitor']
|
||||
},
|
||||
{
|
||||
field: 'time',
|
||||
title: lang['Time Created']
|
||||
},
|
||||
{
|
||||
field: 'size',
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
field: 'buttons',
|
||||
title: ''
|
||||
}
|
||||
],
|
||||
data: data.videos.map((file) => {
|
||||
return {
|
||||
monitorName: `<b>${loadedMonitors[file.mid]?.name || file.mid}</b>`,
|
||||
time: `
|
||||
<div><b>${lang.Started}</b> ${formattedTime(file.start, 'DD-MM-YYYY hh:mm:ss AA')}</div>
|
||||
<div><b>${lang.Ended}</b> ${formattedTime(file.end, 'DD-MM-YYYY hh:mm:ss AA')}</div>
|
||||
`,
|
||||
size: convertKbToHumanSize(file.size),
|
||||
buttons: `
|
||||
<a class="btn btn-sm btn-primary" href="${file.href}" download title="${lang.Download}"><i class="fa fa-download"></i></a>
|
||||
<a class="btn btn-sm btn-primary preview-video" href="${file.href}" title="${lang.Play}"><i class="fa fa-play"></i></a>
|
||||
`,
|
||||
}
|
||||
})
|
||||
if(!usePreloadedData){
|
||||
loadedVideosTable = (await getVideos({
|
||||
monitorId,
|
||||
startDate,
|
||||
endDate,
|
||||
searchQuery,
|
||||
customVideoSet: wantCloudVideos() ? 'cloudVideos' : null,
|
||||
})).videos;
|
||||
$.each(loadedVideosTable,function(n,v){
|
||||
loadedVideosInMemory[`${monitorId}${v.time}`]
|
||||
})
|
||||
}
|
||||
// for (let i = 0; i < loadedVideosTable.length; i++) {
|
||||
// const file = loadedVideosTable[i]
|
||||
// const frameUrl = await getSnapshotFromVideoTimeFrame(file.mid,file.time,file.end);
|
||||
// file.frameUrl = frameUrl
|
||||
// }
|
||||
videosTableDrawArea.bootstrapTable('destroy')
|
||||
videosTableDrawArea.bootstrapTable({
|
||||
onPostBody: loadFramesForVideosInView,
|
||||
onPageChange: () => {
|
||||
setTimeout(() => {
|
||||
loadFramesForVideosInView()
|
||||
},500)
|
||||
},
|
||||
pagination: true,
|
||||
search: true,
|
||||
columns: [
|
||||
{
|
||||
field: 'mid',
|
||||
title: '',
|
||||
checkbox: true,
|
||||
formatter: () => {
|
||||
return {
|
||||
checked: false
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'image',
|
||||
title: '',
|
||||
},
|
||||
{
|
||||
field: 'Monitor',
|
||||
title: '',
|
||||
},
|
||||
{
|
||||
field: 'time',
|
||||
title: lang['Time Created'],
|
||||
},
|
||||
{
|
||||
field: 'end',
|
||||
title: lang['Ended']
|
||||
},
|
||||
{
|
||||
field: 'objects',
|
||||
title: lang['Objects Found']
|
||||
},
|
||||
{
|
||||
field: 'size',
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
field: 'buttons',
|
||||
title: ''
|
||||
}
|
||||
],
|
||||
data: loadedVideosTable.map((file) => {
|
||||
var href = getFullOrigin(true) + file.href
|
||||
var loadedMonitor = loadedMonitors[file.mid]
|
||||
return {
|
||||
image: `<div class="video-thumbnail" data-mid="${file.mid}" data-ke="${file.ke}" data-time="${file.time}" data-end="${file.end}"></div>`,
|
||||
Monitor: loadedMonitor && loadedMonitor.name ? loadedMonitor.name : file.mid,
|
||||
mid: file.mid,
|
||||
time: formattedTime(file.time, 'DD-MM-YYYY hh:mm:ss AA'),
|
||||
end: formattedTime(file.end, 'DD-MM-YYYY hh:mm:ss AA'),
|
||||
objects: file.objects,
|
||||
size: convertKbToHumanSize(file.size),
|
||||
buttons: `
|
||||
<div class="row-info" data-mid="${file.mid}" data-ke="${file.ke}" data-time="${file.time}" data-filename="${file.filename}">
|
||||
<a class="btn btn-sm btn-primary" href="${href}" download title="${lang.Download}"><i class="fa fa-download"></i></a>
|
||||
<a class="btn btn-sm btn-primary preview-video" href="${href}" title="${lang.Play}"><i class="fa fa-play"></i></a>
|
||||
<a class="btn btn-sm btn-default open-video" href="${href}" title="${lang.Play}"><i class="fa fa-play"></i></a>
|
||||
${permissionCheck('video_delete',file.mid) ? `<a class="btn btn-sm btn-danger delete-video" href="${href}" title="${lang.Delete}"><i class="fa fa-trash-o"></i></a>` : ''}
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
function drawPreviewVideo(href){
|
||||
videosTablePreviewArea.html(`<video class="video_video" style="width:100%" autoplay controls preload loop src="${href}"></video>`)
|
||||
}
|
||||
function getSelectedRows(){
|
||||
var rowsSelected = []
|
||||
videosTableDrawArea.find('[name="btSelectItem"]:checked').each(function(n,checkbox){
|
||||
var rowInfo = $(checkbox).parents('tr').find('.row-info')
|
||||
var monitorId = rowInfo.attr('data-mid')
|
||||
var groupKey = rowInfo.attr('data-ke')
|
||||
var filename = rowInfo.attr('data-filename')
|
||||
rowsSelected.push({
|
||||
mid: monitorId,
|
||||
ke: groupKey,
|
||||
filename: filename,
|
||||
})
|
||||
})
|
||||
return rowsSelected
|
||||
}
|
||||
function wantCloudVideos(){
|
||||
const isChecked = cloudVideoCheckSwitch.val() === 'cloud'
|
||||
return isChecked
|
||||
}
|
||||
$('body')
|
||||
.on('click','.open-videosTable',function(e){
|
||||
e.preventDefault()
|
||||
var monitorId = getRowsMonitorId(this)
|
||||
openTab(`videosTableView`,{},null,null,null,() => {
|
||||
drawMonitorListToSelector(monitorsList)
|
||||
drawMonitorListToSelector(monitorsList,null,null,true)
|
||||
monitorsList.val(monitorId)
|
||||
drawVideosTableViewElements()
|
||||
})
|
||||
|
@ -108,13 +210,83 @@ $(document).ready(function(e){
|
|||
drawPreviewVideo(href)
|
||||
return false;
|
||||
})
|
||||
.on('click','.delete-selected-videos',function(e){
|
||||
e.preventDefault()
|
||||
var videos = getSelectedRows()
|
||||
if(videos.length === 0)return;
|
||||
$.confirm.create({
|
||||
title: lang["Delete Videos"],
|
||||
body: `${lang.DeleteTheseMsg}`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-trash-o"></i> ' + lang.Delete,
|
||||
class: 'btn-danger btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
deleteVideos(videos).then(() => {
|
||||
console.log(`Done Deleting Rows!`)
|
||||
})
|
||||
}
|
||||
});
|
||||
return false;
|
||||
})
|
||||
.on('click','.download-selected-videos',function(e){
|
||||
e.preventDefault()
|
||||
var videos = getSelectedRows()
|
||||
if(videos.length === 0)return;
|
||||
$.confirm.create({
|
||||
title: lang["Batch Download"],
|
||||
body: `${lang.batchDownloadText}`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-check"></i> ' + lang.Yes,
|
||||
class: 'btn-success btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
downloadVideos(videos)
|
||||
}
|
||||
});
|
||||
return false;
|
||||
})
|
||||
.on('click','.pop-img',function(e){
|
||||
e.preventDefault()
|
||||
var videos = getSelectedRows()
|
||||
if(videos.length === 0)return;
|
||||
$.confirm.create({
|
||||
title: lang["Batch Download"],
|
||||
body: `${lang.batchDownloadText}`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-check"></i> ' + lang.Yes,
|
||||
class: 'btn-success btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
downloadVideos(videos)
|
||||
}
|
||||
});
|
||||
return false;
|
||||
})
|
||||
onWebSocketEvent((data) => {
|
||||
switch(data.f){
|
||||
case'video_delete':
|
||||
if(tabTree.name === 'videosTableView' && monitorsList.val() === data.mid){
|
||||
var videoIndexToRemove = loadedVideosTable.findIndex(row => new Date(row.time).getTime() === new Date(data.time).getTime())
|
||||
if(videoIndexToRemove !== -1){
|
||||
loadedVideosTable.splice(videoIndexToRemove, 1);
|
||||
delete(loadedVideosInMemory[`${data.mid}${data.time}`])
|
||||
clearTimeout(redrawTimeout)
|
||||
redrawTimeout = setTimeout(function(){
|
||||
drawVideosTableViewElements(true)
|
||||
},2000)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
})
|
||||
addOnTabOpen('videosTableView', function () {
|
||||
drawMonitorListToSelector(monitorsList)
|
||||
drawMonitorListToSelector(monitorsList,null,null,true)
|
||||
drawVideosTableViewElements()
|
||||
})
|
||||
addOnTabReopen('videosTableView', function () {
|
||||
var theSelected = `${monitorsList.val()}`
|
||||
drawMonitorListToSelector(monitorsList)
|
||||
drawMonitorListToSelector(monitorsList,null,null,true)
|
||||
monitorsList.val(theSelected)
|
||||
})
|
||||
addOnTabAway('videosTableView', function () {
|
||||
|
|
|
@ -14,14 +14,20 @@ var onWebSocketEventFunctions = []
|
|||
function onWebSocketEvent(theAction){
|
||||
onWebSocketEventFunctions.push(theAction)
|
||||
}
|
||||
var queuedCallbacks = {}
|
||||
$(document).ready(function(){
|
||||
mainSocket = io(location.origin,{
|
||||
path: websocketPath,
|
||||
query: websocketQuery
|
||||
})
|
||||
mainSocket.f = function(data){
|
||||
mainSocket.f = function(data,callback){
|
||||
if(!data.ke)data.ke = $user.ke;
|
||||
if(!data.uid)data.uid = $user.uid;
|
||||
if(callback){
|
||||
var callbackId = generateId();
|
||||
data.callbackId = callbackId
|
||||
queuedCallbacks[callbackId] = callback
|
||||
}
|
||||
console.log('Sending Data',data)
|
||||
return mainSocket.emit('f',data)
|
||||
}
|
||||
|
@ -58,6 +64,10 @@ $(document).ready(function(){
|
|||
theAction(d)
|
||||
})
|
||||
break;
|
||||
case'callback':
|
||||
console.log('Callback from Websocket Request',d)
|
||||
queuedCallbacks[d.callbackId](...d.args)
|
||||
break;
|
||||
}
|
||||
$.each(onWebSocketEventFunctions,function(n,theAction){
|
||||
theAction(d)
|
||||
|
|
|
@ -15,3 +15,297 @@
|
|||
.form-group-group table tr:first-child > td {
|
||||
border-top: 0;
|
||||
}
|
||||
.form-group label>div:first-child {width:40%}
|
||||
.list-group li .form-group {margin:0}
|
||||
pre ul {margin:0;padding:0;list-style: none;}
|
||||
pre ul > li > ul {padding-left: 10px!important;}
|
||||
a {cursor:pointer}
|
||||
.flex-grow-1 {
|
||||
flex: 1!important;
|
||||
}
|
||||
.card .card-body {
|
||||
min-height: auto;
|
||||
}
|
||||
.flex-padding.d-flex.flex-row > div {
|
||||
padding: 1rem;
|
||||
}
|
||||
/* Now Navbar */
|
||||
|
||||
.nav-tabs {
|
||||
border: 0;
|
||||
padding: 15px 0.7rem;
|
||||
}
|
||||
|
||||
.nav-tabs>.nav-item>.nav-link {
|
||||
color: #888;
|
||||
margin: 0;
|
||||
margin-right: 5px;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 30px;
|
||||
font-size: 14px;
|
||||
padding: 11px 23px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.nav-tabs>.nav-item>.nav-link:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.nav-tabs>.nav-item>.nav-link.active {
|
||||
border: 1px solid #888;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.nav-tabs>.nav-item>.nav-link i.now-ui-icons {
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
.nav-tabs>.nav-item.disabled>.nav-link,
|
||||
.nav-tabs>.nav-item.disabled>.nav-link:hover {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.nav-tabs.nav-tabs-neutral>.nav-item>.nav-link {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.nav-tabs.nav-tabs-neutral>.nav-item>.nav-link.active {
|
||||
border-color: rgba(255, 255, 255, 0.5);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.nav-tabs.nav-tabs-primary>.nav-item>.nav-link.active {
|
||||
border-color: #f96332;
|
||||
color: #f96332;
|
||||
}
|
||||
|
||||
.nav-tabs.nav-tabs-info>.nav-item>.nav-link.active {
|
||||
border-color: #2CA8FF;
|
||||
color: #2CA8FF;
|
||||
}
|
||||
|
||||
.nav-tabs.nav-tabs-danger>.nav-item>.nav-link.active {
|
||||
border-color: #FF3636;
|
||||
color: #FF3636;
|
||||
}
|
||||
|
||||
.nav-tabs.nav-tabs-warning>.nav-item>.nav-link.active {
|
||||
border-color: #FFB236;
|
||||
color: #FFB236;
|
||||
}
|
||||
|
||||
.nav-tabs.nav-tabs-success>.nav-item>.nav-link.active {
|
||||
border-color: #18ce0f;
|
||||
color: #18ce0f;
|
||||
}
|
||||
|
||||
.navbar-rounded {
|
||||
border-radius: 15px;
|
||||
}
|
||||
.navbar {
|
||||
padding-top: 0.625rem;
|
||||
padding-bottom: 0.625rem;
|
||||
min-height: 53px;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.navbar a:not(.btn):not(.dropdown-item) {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.navbar p {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav.navbar-logo {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 49px;
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link.btn {
|
||||
padding: 11px 22px;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link.btn.btn-lg {
|
||||
padding: 15px 48px;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link.btn.btn-sm {
|
||||
padding: 5px 15px;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link:not(.btn) {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.7142em;
|
||||
padding: 0.5rem 0.7rem;
|
||||
line-height: 1.625rem;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link:not(.btn) i.fa+p,
|
||||
.navbar .navbar-nav .nav-link:not(.btn) i.now-ui-icons+p {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link:not(.btn) i.fa,
|
||||
.navbar .navbar-nav .nav-link:not(.btn) i.now-ui-icons {
|
||||
font-size: 18px;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
text-align: center;
|
||||
width: 21px;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link:not(.btn) i.now-ui-icons {
|
||||
top: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link:not(.btn).profile-photo .profile-photo-small {
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link:not(.btn).disabled {
|
||||
opacity: .5;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-item.active .nav-link:not(.btn),
|
||||
.navbar .navbar-nav .nav-item .nav-link:not(.btn):focus,
|
||||
.navbar .navbar-nav .nav-item .nav-link:not(.btn):hover,
|
||||
.navbar .navbar-nav .nav-item .nav-link:not(.btn):active {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.1875rem;
|
||||
}
|
||||
|
||||
.navbar .logo-container {
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
overflow: hidden;
|
||||
margin: 0 auto;
|
||||
border-radius: 50%;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.navbar .navbar-brand {
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8571em;
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
line-height: 1.625rem;
|
||||
}
|
||||
|
||||
.navbar .navbar-toggler {
|
||||
width: 37px;
|
||||
height: 27px;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.navbar .navbar-toggler.navbar-toggler-left {
|
||||
position: relative;
|
||||
left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.navbar .navbar-toggler:hover .navbar-toggler-bar.bar2 {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
.navbar .button-dropdown .navbar-toggler-bar:nth-child(2) {
|
||||
width: 17px;
|
||||
}
|
||||
|
||||
.navbar.navbar-transparent {
|
||||
background-color: transparent !important;
|
||||
box-shadow: none;
|
||||
color: #FFFFFF;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.navbar.bg-white:not(.navbar-transparent) a:not(.dropdown-item) {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.navbar.bg-white:not(.navbar-transparent) a:not(.dropdown-item).disabled {
|
||||
opacity: .5;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.navbar.bg-white:not(.navbar-transparent) .button-bar {
|
||||
background: #888;
|
||||
}
|
||||
|
||||
.navbar.bg-white:not(.navbar-transparent) .nav-item.active .nav-link:not(.btn),
|
||||
.navbar.bg-white:not(.navbar-transparent) .nav-item .nav-link:not(.btn):focus,
|
||||
.navbar.bg-white:not(.navbar-transparent) .nav-item .nav-link:not(.btn):hover,
|
||||
.navbar.bg-white:not(.navbar-transparent) .nav-item .nav-link:not(.btn):active {
|
||||
background-color: rgba(222, 222, 222, 0.3);
|
||||
}
|
||||
|
||||
.navbar.bg-white:not(.navbar-transparent) .logo-container {
|
||||
border: 1px solid #888;
|
||||
}
|
||||
|
||||
/* .bg-default {
|
||||
background-color: #888 !important;
|
||||
}
|
||||
|
||||
.bg-primary {
|
||||
background-color: #f96332 !important;
|
||||
}
|
||||
|
||||
.bg-info {
|
||||
background-color: #2CA8FF !important;
|
||||
}
|
||||
|
||||
.bg-success {
|
||||
background-color: #18ce0f !important;
|
||||
}
|
||||
|
||||
.bg-danger {
|
||||
background-color: #FF3636 !important;
|
||||
}
|
||||
|
||||
.bg-warning {
|
||||
background-color: #FFB236 !important;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #FFFFFF !important;
|
||||
} */
|
||||
/* Slim Style */
|
||||
.form-control {
|
||||
/* background-color: transparent;
|
||||
border: 1px solid #E3E3E3;
|
||||
border-radius: 30px;
|
||||
color: #2c2c2c;
|
||||
line-height: normal;
|
||||
font-size: 0.8571em;
|
||||
box-shadow: none; */
|
||||
}
|
||||
.btn-round {
|
||||
/* border-width: 1px; */
|
||||
/* border-radius: 30px !important; */
|
||||
}
|
||||
.btn, .navbar .navbar-nav>a.btn {
|
||||
font-weight: 400;
|
||||
line-height: 1.35em;
|
||||
margin: 5px 1px;
|
||||
border: none;
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
margin: 10px 10px 20px 0;
|
||||
}
|
||||
.better-json-editor h3 > span{
|
||||
color: #bd4147;
|
||||
background-color: #f8f9fa;
|
||||
color: #fff;
|
||||
background-color: #132232;
|
||||
padding: 7px 10px;
|
||||
border-radius: .25rem;
|
||||
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
|
@ -14,12 +14,13 @@
|
|||
margin-left:20px;
|
||||
}
|
||||
.better-json-editor label, .better-json-editor .control-label {
|
||||
color: #bd4147;
|
||||
background-color: #f8f9fa;
|
||||
padding: 7px 10px;
|
||||
border-radius: .25rem;
|
||||
color: #fff;
|
||||
background-color: #132232;
|
||||
padding: 5px;
|
||||
border-radius: 0.25rem;
|
||||
font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
margin-bottom:5px;
|
||||
margin-bottom: 5px;
|
||||
font-size: 80%;
|
||||
}
|
||||
.better-json-editor .floating-json {
|
||||
padding: 10px;
|
||||
|
@ -34,7 +35,13 @@
|
|||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
background: #fbfbfb;
|
||||
background: #132232;
|
||||
color: #fff;
|
||||
}
|
||||
.better-json-editor table tr,
|
||||
.better-json-editor table th,
|
||||
.better-json-editor table.table-bordered {
|
||||
border: 0;
|
||||
}
|
||||
.better-json-editor .floating-json textarea.form-control {
|
||||
padding: 20px;
|
||||
|
@ -58,9 +65,19 @@
|
|||
margin: 0;
|
||||
}
|
||||
.better-json-editor .row > div {
|
||||
border: 1px solid #eee;
|
||||
border: 0;
|
||||
background-color: #1e2b37 !important;
|
||||
border-radius: 5px;
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.better-json-editor .badge.badge-default {
|
||||
background: #ffffff;
|
||||
color: #333;
|
||||
border: 0;
|
||||
padding: 5px 7px 5px 5px;
|
||||
}
|
||||
.better-json-editor .fa-caret-square-o-down:before {
|
||||
content: "\f0d7";
|
||||
}
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
#easyRemoteAccess .card {
|
||||
cursor: pointer;
|
||||
#easyRemoteAccess .card * {
|
||||
transition: none;
|
||||
}
|
||||
#easyRemoteAccess .card.active {
|
||||
background-color: #18ce0f !important;
|
||||
background-color: #088c4c !important;
|
||||
color: #fff;
|
||||
}
|
||||
#easyRemoteAccess .card.active *{
|
||||
#easyRemoteAccess .card.active * {
|
||||
color: #fff!important;
|
||||
}
|
||||
#easyRemoteAccess .card.active .card-header,
|
||||
|
@ -45,6 +44,15 @@
|
|||
#easyRemoteAccess .card.selected .selected-badge {
|
||||
display: inline-block;
|
||||
}
|
||||
#easyRemoteAccess .card.active .table-striped tbody tr:nth-of-type(odd) {
|
||||
#p2pServerList .card.active .table-striped tbody tr:nth-of-type(odd) {
|
||||
background-color: rgb(49 204 68 / 5%);
|
||||
}
|
||||
#p2pServerList .card .d-flex.flex-row > div {
|
||||
padding: 1rem;
|
||||
}
|
||||
#p2pServerList .card.active .d-flex.flex-row .epic-text-filter {
|
||||
font-weight: bold;
|
||||
}
|
||||
#p2pServerList .card .d-flex.flex-row:nth-of-type(odd) {
|
||||
background: rgba(0,0,0,0.1)
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -14,11 +14,11 @@ $(document).ready(function(){
|
|||
}else{
|
||||
listElement.append(`
|
||||
<div class="col-md-12">
|
||||
<div class="card" package-name="${module.name}">
|
||||
<div class="card bg-dark" package-name="${module.name}">
|
||||
<div class="card-body">
|
||||
<div><h4 class="title mt-0">${humanName}</h4></div>
|
||||
<div><pre><b>${lang['Time Created']} :</b> ${module.created}</pre></div>
|
||||
<div><pre><b>${lang['Last Modified']} :</b> ${module.lastModified}</pre></div>
|
||||
<div class="pb-2"><b>${lang['Time Created']} :</b> ${module.created}</div>
|
||||
<div class="pb-2"><b>${lang['Last Modified']} :</b> ${module.lastModified}</div>
|
||||
<div class="mb-2">
|
||||
${!module.isIgnitor ? `
|
||||
${module.hasInstaller ? `
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
$(document).ready(function(){
|
||||
var easyRemoteAccessTab = $('#easyRemoteAccess')
|
||||
var toggleAffected = easyRemoteAccessTab.find('.p2p-toggle-affected')
|
||||
var p2pEnabledSwitch = easyRemoteAccessTab.find('[name="p2pEnabled"]')
|
||||
var p2pHostSelectedContainer = $('#p2pHostSelected')
|
||||
var easyRemoteAccessForm = easyRemoteAccessTab.find('form')
|
||||
var remoteDashboardLinkButton = easyRemoteAccessTab.find('.remote-dashboard-link')
|
||||
var loadingRegistration = false
|
||||
var statusConnections = {}
|
||||
var currentlyRegisteredP2PServer = currentlySelectedP2PServerId ? currentlySelectedP2PServerId + '' : undefined
|
||||
function copyToClipboard(str) {
|
||||
const el = document.createElement('textarea');
|
||||
|
@ -58,6 +61,7 @@ $(document).ready(function(){
|
|||
connectedUsers.text(data.users)
|
||||
chartViewerCount.text(data.statViewers)
|
||||
})
|
||||
statusConnections[key] = socketConnection
|
||||
}
|
||||
function disableForm(){
|
||||
loadingRegistration = true
|
||||
|
@ -95,6 +99,31 @@ $(document).ready(function(){
|
|||
})
|
||||
}
|
||||
}
|
||||
function beginAllStatusConnections(){
|
||||
$.each(p2pServerList,function(key,server){
|
||||
server.key = key
|
||||
if(window.useBetterP2P && !server.v2)return;
|
||||
if(!window.useBetterP2P && server.v2)return;
|
||||
beginStatusConnectionForServer(key,server)
|
||||
})
|
||||
}
|
||||
function closeAllStatusConnections(){
|
||||
$.each(statusConnections,function(key,server){
|
||||
server.disconnect()
|
||||
delete(statusConnections[key])
|
||||
})
|
||||
}
|
||||
function setVisibilityForList(){
|
||||
var isOn = p2pEnabledSwitch.val() === '1'
|
||||
if(isOn){
|
||||
beginAllStatusConnections()
|
||||
toggleAffected.show()
|
||||
}else{
|
||||
closeAllStatusConnections()
|
||||
toggleAffected.hide()
|
||||
}
|
||||
}
|
||||
p2pEnabledSwitch.change(setVisibilityForList)
|
||||
easyRemoteAccessTab.find('.submit').click(function(){
|
||||
easyRemoteAccessForm.submit()
|
||||
})
|
||||
|
@ -152,11 +181,6 @@ $(document).ready(function(){
|
|||
}
|
||||
return false;
|
||||
})
|
||||
$.each(p2pServerList,function(key,server){
|
||||
server.key = key
|
||||
if(window.useBetterP2P && !server.v2)return;
|
||||
if(!window.useBetterP2P && server.v2)return;
|
||||
beginStatusConnectionForServer(key,server)
|
||||
})
|
||||
setVisibilityForList()
|
||||
displayCurrentlySelectedInternally()
|
||||
})
|
||||
|
|
|
@ -14,11 +14,11 @@ $(document).ready(function(){
|
|||
}else{
|
||||
listElement.append(`
|
||||
<div class="col-md-12">
|
||||
<div class="card" package-name="${module.name}">
|
||||
<div class="card-body">
|
||||
<div class="card bg-dark text-white mb-3" package-name="${module.name}">
|
||||
<div class="card-body pb-3">
|
||||
<div><h4 class="title mt-0">${humanName}</h4></div>
|
||||
<div><pre><b>${lang['Time Created']} :</b> ${module.created}</pre></div>
|
||||
<div><pre><b>${lang['Last Modified']} :</b> ${module.lastModified}</pre></div>
|
||||
<div class="pb-2"><b>${lang['Time Created']} :</b> ${module.created}</div>
|
||||
<div class="pb-2"><b>${lang['Last Modified']} :</b> ${module.lastModified}</div>
|
||||
<div class="mb-2">
|
||||
${module.hasInstaller ? `
|
||||
<a class="btn btn-sm btn-info" plugin-manager-action="install">${lang['Run Installer']}</a>
|
||||
|
@ -30,8 +30,8 @@ $(document).ready(function(){
|
|||
</div>
|
||||
<div class="pl-2 pr-2">
|
||||
<div class="install-output row">
|
||||
<div class="col-md-6 pr-2"><pre class="install-output-stdout"></pre></div>
|
||||
<div class="col-md-6 pl-2"><pre class="install-output-stderr"></pre></div>
|
||||
<div class="col-md-6 pr-2"><pre class="install-output-stdout text-white mb-0"></pre></div>
|
||||
<div class="col-md-6 pl-2"><pre class="install-output-stderr text-white mb-0"></pre></div>
|
||||
</div>
|
||||
<div class="command-installer row" style="display:none">
|
||||
<div class="col-md-6">
|
||||
|
|
|
@ -1,12 +1,55 @@
|
|||
$(document).ready(function(){
|
||||
var changeSuperPreferencesTab = $('#changeSuperPreferences')
|
||||
var tokenContainer = $('#super-tokens')
|
||||
var changeSuperPreferencesForm = changeSuperPreferencesTab.find('form')
|
||||
function generateId(x){
|
||||
if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
for( var i=0; i < x; i++ )
|
||||
t += p.charAt(Math.floor(Math.random() * p.length));
|
||||
return t;
|
||||
}
|
||||
function drawTokenRow(tokenValue){
|
||||
var html = `<div class="d-flex flex-row token-row mt-3">
|
||||
<div class="flex-grow-1">
|
||||
<input class="form-control form-control-sm token-row-input" value="${tokenValue || generateId(30)}">
|
||||
</div>
|
||||
<div>
|
||||
<a class="delete-token btn btn-danger btn-sm my-0 ml-3"><i class="fa fa-trash"></i></a>
|
||||
</div>
|
||||
</div>`
|
||||
tokenContainer.append(html)
|
||||
}
|
||||
function getTokenRows(){
|
||||
var rowsFound = []
|
||||
changeSuperPreferencesTab.find('.token-row-input').each(function(n,v){
|
||||
var el = $(v)
|
||||
var tokenValue = el.val().trim()
|
||||
if(tokenValue)rowsFound.push(tokenValue);
|
||||
})
|
||||
return rowsFound
|
||||
}
|
||||
function loadPreferences(){
|
||||
var tokens = $user.tokens
|
||||
changeSuperPreferencesTab.find('[name=mail]').val($user.mail)
|
||||
if(tokens instanceof Array){
|
||||
tokens.forEach(function(token){
|
||||
drawTokenRow(token)
|
||||
})
|
||||
}
|
||||
}
|
||||
changeSuperPreferencesTab.find('.new-token').click(function(){
|
||||
drawTokenRow()
|
||||
})
|
||||
changeSuperPreferencesTab.on('click','.delete-token',function(){
|
||||
$(this).parents('.token-row').remove()
|
||||
})
|
||||
changeSuperPreferencesTab.find('.submit').click(function(){
|
||||
changeSuperPreferencesForm.submit()
|
||||
})
|
||||
changeSuperPreferencesForm.submit(function(e){
|
||||
e.preventDefault()
|
||||
var formValues = $(this).serializeObject()
|
||||
formValues.tokens = getTokenRows()
|
||||
// $.ccio.cx({f:'accounts',ff:'saveSuper',form:formValues})
|
||||
$.post(superApiPrefix + $user.sessionKey + '/accounts/saveSettings',{
|
||||
data: JSON.stringify(formValues)
|
||||
|
@ -15,4 +58,5 @@ $(document).ready(function(){
|
|||
})
|
||||
return false
|
||||
})
|
||||
loadPreferences()
|
||||
})
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue