Motostoke City
parent
e197f0674f
commit
fe818a73a8
|
@ -0,0 +1,55 @@
|
|||
docker-latest-build:
|
||||
image: docker:latest
|
||||
stage: build
|
||||
variables:
|
||||
BASE_IMAGE: "node:20-bullseye-slim"
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
script:
|
||||
- |
|
||||
BRANCH_PREFIX="$(if [ $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH ]; then echo ${CI_COMMIT_REF_SLUG} ; else echo 'latest'; fi)"
|
||||
BASE_IMAGE_ARCH="$(echo ${BASE_IMAGE} | cut -d'/' -f1)"
|
||||
BASE_IMAGE_TYPE="$(echo ${BASE_IMAGE} | cut -d'/' -f2)"
|
||||
ARCH_TYPE="$(if [ $BASE_IMAGE_ARCH != $BASE_IMAGE_TYPE ]; then echo '-'${BASE_IMAGE_ARCH}; else echo ''; fi )"
|
||||
|
||||
export IMAGE_NAME="$CI_REGISTRY_IMAGE:${BRANCH_PREFIX}${ARCH_TYPE}${NO_DB_SUFIX}"
|
||||
echo "Running on branch '${CI_COMMIT_BRANCH}', Image: ${IMAGE_NAME}" ;
|
||||
|
||||
- docker build --pull --build-arg BASE_IMAGE="${BASE_IMAGE}" -t "${IMAGE_NAME}" . -f "Dockerfile"
|
||||
- docker push "${IMAGE_NAME}"
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH != "master" && $CI_COMMIT_BRANCH != "main" && $CI_COMMIT_BRANCH != "dev"
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
exists:
|
||||
- Dockerfile
|
||||
|
||||
docker-nvidia-build:
|
||||
image: docker:latest
|
||||
stage: build
|
||||
variables:
|
||||
BASE_IMAGE: "nvidia/cuda:11.7.1-cudnn8-runtime-ubuntu22.04"
|
||||
services:
|
||||
- docker:dind
|
||||
before_script:
|
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
|
||||
script:
|
||||
- |
|
||||
BRANCH_PREFIX="$(if [ $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH ]; then echo ${CI_COMMIT_REF_SLUG} ; else echo 'latest'; fi)"
|
||||
BASE_IMAGE_ARCH="$(echo ${BASE_IMAGE} | cut -d'/' -f1)"
|
||||
BASE_IMAGE_TYPE="$(echo ${BASE_IMAGE} | cut -d'/' -f2)"
|
||||
ARCH_TYPE="$(if [ $BASE_IMAGE_ARCH != $BASE_IMAGE_TYPE ]; then echo '-'${BASE_IMAGE_ARCH}; else echo ''; fi )"
|
||||
|
||||
export IMAGE_NAME="$CI_REGISTRY_IMAGE:${BRANCH_PREFIX}${ARCH_TYPE}${NO_DB_SUFIX}"
|
||||
echo "Running on branch '${CI_COMMIT_BRANCH}', Image: ${IMAGE_NAME}" ;
|
||||
|
||||
- docker build --pull --build-arg BASE_IMAGE="${BASE_IMAGE}" -t "${IMAGE_NAME}" . -f "Dockerfile"
|
||||
- docker push "${IMAGE_NAME}"
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH != "master" && $CI_COMMIT_BRANCH != "main" && $CI_COMMIT_BRANCH != "dev"
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
exists:
|
||||
- Dockerfile
|
|
@ -1,6 +1,8 @@
|
|||
⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
|
||||
# OUTDATED, Newest Docker method here :
|
||||
|
||||
# This Docker method is only for integrated Database and simple volume mounts. For separate database and more elaborate installation please use this registry instead :
|
||||
https://gitlab.com/Shinobi-Systems/ShinobiDocker
|
||||
|
||||
⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
|
||||
|
||||
|
||||
|
@ -33,27 +35,9 @@ Once complete open port `8080` of your Docker host in a web browser.
|
|||
> 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' registry.gitlab.com/shinobi-systems/shinobi:dev
|
||||
docker run -d --name='Shinobi' --memory=2g -p '8080:8080/tcp' -p '21:21/tcp' -v "$HOME/ShinobiDatabase":'/var/lib/mysql':'rw' -v "$HOME/Shinobi":'/home/Shinobi':'rw' registry.gitlab.com/shinobi-systems/shinobi:dev
|
||||
```
|
||||
|
||||
**Installing Object Detection (TensorFlow.js)**
|
||||
*Updated Image only works with Dashboard v3*
|
||||
|
||||
> 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.
|
||||
|
||||
- `-p '8082:8082/tcp'` is an optional flag if you decide to run the plugin in host mode.
|
||||
- `-e PLUGIN_HOST='10.1.103.113'` Set this as your Shinobi IP Address.
|
||||
- `-e PLUGIN_PORT='8080'` Set this as your Shinobi Web Port number.
|
||||
|
||||
```
|
||||
docker run -d --name='shinobi-tensorflow' -e PLUGIN_HOST='10.1.103.113' -e PLUGIN_PORT='8080' -v "$HOME/Shinobi/docker-plugins/tensorflow":'/config':'rw' registry.gitlab.com/shinobi-systems/docker-plugin-tensorflow.js:master
|
||||
```
|
||||
|
||||
More Information about this plugin :
|
||||
- CPU : https://gitlab.com/Shinobi-Systems/docker-plugin-tensorflow.js
|
||||
- GPU (NVIDIA CUDA) : https://gitlab.com/Shinobi-Systems/docker-plugin-tensorflow.js/-/tree/gpu
|
||||
|
||||
|
||||
## From Source
|
||||
> Image is based on Ubuntu Bionic (20.04). Node.js 12 is used. MariaDB and FFmpeg are included.
|
||||
|
||||
|
@ -86,27 +70,18 @@ docker build -f Dockerfile.arm32v7 --tag shinobi-image:1.0 .
|
|||
> This command only works on Linux because of the temporary directory used. This location must exist in RAM. `-v "/dev/shm/shinobiStreams":'/dev/shm/streams':'rw'`. The timezone is also acquired from the host by the volume declaration of `-v '/etc/localtime':'/etc/localtime':'ro'`.
|
||||
|
||||
```
|
||||
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' shinobi-image:1.0
|
||||
docker run -d --name='Shinobi' --memory=2g -p '8080:8080/tcp' -p '21:21/tcp' -v "$HOME/ShinobiDatabase":'/var/lib/mysql':'rw' -v "$HOME/Shinobi":'/home/Shinobi':'rw' shinobi-image:1.0
|
||||
```
|
||||
|
||||
> Host mount paths have been updated in this document.
|
||||
|
||||
### Running without Included Database (NoDB)
|
||||
|
||||
For information about this please see this Merge Request done by @thtmnisamnstr. It thoroughly documents how to use the NoDB installation method.
|
||||
|
||||
https://gitlab.com/Shinobi-Systems/Shinobi/-/merge_requests/443
|
||||
|
||||
### Volumes
|
||||
|
||||
| Volumes | Description |
|
||||
|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| /dev/shm/Shinobi/streams | **IMPORTANT!** This must be mapped to somewhere in the host's RAM. When running this image on Windows you will need to select a different location. |
|
||||
| /config | Put `conf.json` or `super.json` files in here to override the default. values. |
|
||||
| $HOME/Shinobi/customAutoLoad | Maps to the `/home/Shinobi/libs/customAutoLoad` folder for loading your own modules into Shinobi. |
|
||||
| $HOME/Shinobi/database | A map to `/var/lib/mysql` in the container. This is the database's core files. |
|
||||
| $HOME/Shinobi/videos | A map to `/home/Shinobi/videos`. The storage location of your recorded videos. |
|
||||
| $HOME/Shinobi/plugins | A map to `/home/Shinobi/plugins`. Mapped so that plugins can easily be modified or swapped. |
|
||||
| $HOME/Shinobi | Maps to the `/home/Shinobi` folder for the `customAutoLoad`, `plugins`, and `videos` folders inside and other files. Additionally you can edit the conf.json and super.json here. |
|
||||
| $HOME/ShinobiDatabase | A map to `/var/lib/mysql` in the container. This is the database's core files. |
|
||||
|
||||
### Environment Variables
|
||||
|
||||
|
|
|
@ -66,37 +66,23 @@ fi
|
|||
|
||||
DATABASE_CONFIG='{"host": "'$DB_HOST'","user": "'$DB_USER'","password": "'$DB_PASSWORD'","database": "'$DB_DATABASE'","port":'$DB_PORT'}'
|
||||
|
||||
cronKey="$(head -c 1024 < /dev/urandom | sha256sum | awk '{print substr($1,1,29)}')"
|
||||
|
||||
cd /home/Shinobi
|
||||
mkdir -p libs/customAutoLoad
|
||||
|
||||
if [ -e "/config/conf.json" ]; then
|
||||
cp /config/conf.json conf.json
|
||||
elif [ ! -e "./conf.json" ]; then
|
||||
# Create /config/conf.json if it doesn't exist
|
||||
if [ ! -e "./conf.json" ]; then
|
||||
cp conf.sample.json conf.json
|
||||
fi
|
||||
# Create /config/conf.json if it doesn't exist
|
||||
if [ ! -e "/config/conf.json" ]; then
|
||||
node tools/modifyConfiguration.js cpuUsageMarker=CPU subscriptionId=$SUBSCRIPTION_ID thisIsDocker=true pluginKeys="$PLUGIN_KEYS" databaseType="$DB_TYPE" db="$DATABASE_CONFIG" ssl="$SSL_CONFIG"
|
||||
cp /config/conf.json conf.json
|
||||
fi
|
||||
sed -i -e 's/change_this_to_something_very_random__just_anything_other_than_this/'"$cronKey"'/g' conf.json
|
||||
|
||||
node tools/modifyConfiguration.js cpuUsageMarker=CPU subscriptionId=$SUBSCRIPTION_ID pluginKeys="$PLUGIN_KEYS" databaseType="$DB_TYPE" db="$DATABASE_CONFIG" ssl="$SSL_CONFIG"
|
||||
|
||||
echo "============="
|
||||
echo "Default Superuser : admin@shinobi.video"
|
||||
echo "Default Password : admin"
|
||||
echo "Log in at http://HOST_IP:SHINOBI_PORT/super"
|
||||
if [ -e "/config/super.json" ]; then
|
||||
cp /config/super.json super.json
|
||||
elif [ ! -e "./super.json" ]; then
|
||||
cp super.sample.json super.json
|
||||
fi
|
||||
|
||||
if [ -e "/config/init.extension.sh" ]; then
|
||||
echo "Running extension init file ..."
|
||||
( sh /config/init.extension.sh )
|
||||
if [ ! -e "./super.json" ]; then
|
||||
cp super.sample.json super.json
|
||||
fi
|
||||
|
||||
# Execute Command
|
||||
|
|
|
@ -63,9 +63,8 @@ RUN sed -i -e 's/\r//g' /home/Shinobi/Docker/init.sh
|
|||
RUN apt-get update -y --fix-missing
|
||||
RUN apt-get upgrade -y
|
||||
|
||||
VOLUME ["/home/Shinobi/videos"]
|
||||
VOLUME ["/home/Shinobi/libs/customAutoLoad"]
|
||||
VOLUME ["/config"]
|
||||
VOLUME ["/home/Shinobi"]
|
||||
VOLUME ["/var/lib/mysql"]
|
||||
|
||||
EXPOSE 8080 443 21 25
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ representative at support@shinobi.systems.
|
|||
|
||||
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.
|
||||
- 15 Active Monitor Rule : Having at least 15 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.
|
||||
|
||||
|
|
|
@ -91,10 +91,14 @@ require('./libs/ffmpeg.js')(s,config,lang, async () => {
|
|||
require('./libs/auth/logins.js')(s,config,lang,app)
|
||||
//rally other Shinobi
|
||||
require('./libs/rally.js')(s,config,lang,app,io)
|
||||
//on-start actions, daemon(s) starter
|
||||
await require('./libs/startup.js')(s,config,lang)
|
||||
//rally other Shinobi
|
||||
require('./libs/mountManager.js')(s,config,lang,app,io)
|
||||
// management server connect
|
||||
require('./libs/connectToManagementServer/index.js')(s,config,lang,app)
|
||||
//p2p, commander
|
||||
require('./libs/commander.js')(s,config,lang,app)
|
||||
//on-start actions, daemon(s) starter
|
||||
await require('./libs/startup.js')(s,config,lang)
|
||||
//cron
|
||||
require('./libs/cron.js')(s,config,lang)
|
||||
//video browser functions
|
||||
|
|
|
@ -125,6 +125,12 @@ module.exports = function(s,config,lang){
|
|||
"fieldType": "select",
|
||||
"possible": s.listOfStorage
|
||||
},
|
||||
{
|
||||
"name": "detail=ptz_id",
|
||||
"field": lang["PTZ Control ID"],
|
||||
"example": "1",
|
||||
"description": lang["ptzControlIdFieldText"],
|
||||
},
|
||||
{
|
||||
"name": "detail=auto_compress_videos",
|
||||
"field": lang['Compress Completed Videos'],
|
||||
|
@ -307,6 +313,10 @@ module.exports = function(s,config,lang){
|
|||
"name": "RTSP",
|
||||
"value": "rtsp"
|
||||
},
|
||||
{
|
||||
"name": "SRT",
|
||||
"value": "srt"
|
||||
},
|
||||
{
|
||||
"name": "RTMP",
|
||||
"value": "rtmp"
|
||||
|
@ -4472,8 +4482,8 @@ module.exports = function(s,config,lang){
|
|||
"default": "0",
|
||||
"example": "",
|
||||
"fieldType": "select",
|
||||
"attribute": "multiple",
|
||||
id: 'copy_settings_monitors',
|
||||
"attribute": `copy="#monSectionLogging" style="min-height:100px" multiple`,
|
||||
"form-group-class": "h_copy_settings_input h_copy_settings_1",
|
||||
"possible": [
|
||||
{
|
||||
|
@ -4973,10 +4983,36 @@ module.exports = function(s,config,lang){
|
|||
"name": lang['Live Grid'],
|
||||
"color": "navy",
|
||||
"info": [
|
||||
{
|
||||
"field": lang['Force Monitors Per Row'],
|
||||
attribute:'localStorage="montage_use"',
|
||||
selector:'st_force_mon_rows',
|
||||
"description": "",
|
||||
"default": "0",
|
||||
"example": "",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"field": lang['Monitors per row'],
|
||||
"placeholder": "3",
|
||||
"form-group-class":"st_force_mon_rows_input st_force_mon_rows_1",
|
||||
attribute:'localStorage="montage"',
|
||||
"placeholder": "3",
|
||||
},
|
||||
{
|
||||
"field": lang.hlsOptions,
|
||||
"name": "localStorage=hlsOptions",
|
||||
fieldType:"textarea",
|
||||
"placeholder": "{}",
|
||||
},
|
||||
]
|
||||
},
|
||||
|
@ -4984,46 +5020,20 @@ module.exports = function(s,config,lang){
|
|||
"name": lang.Preferences,
|
||||
"color": "navy",
|
||||
"info": [
|
||||
{
|
||||
{
|
||||
"field": lang['Clock Format'],
|
||||
"name": "detail=clock_date_format",
|
||||
"placeholder": "$DAYNAME $DAY $MONTHNAME $YEAR",
|
||||
"name": "detail=clock_date_format",
|
||||
"placeholder": "$DAYNAME $DAY $MONTHNAME $YEAR",
|
||||
},
|
||||
{
|
||||
"field": lang.CSS,
|
||||
"name": "detail=css",
|
||||
fieldType:"textarea",
|
||||
"placeholder": "#main_header{background:#b59f00}",
|
||||
"description": "",
|
||||
"default": "",
|
||||
"example": "",
|
||||
"possible": ""
|
||||
},
|
||||
{
|
||||
"field": lang.hlsOptions,
|
||||
"name": "localStorage=hlsOptions",
|
||||
"field": lang.CSS,
|
||||
"name": "detail=css",
|
||||
fieldType:"textarea",
|
||||
"placeholder": "{}",
|
||||
},
|
||||
{
|
||||
"field": lang['Force Monitors Per Row'],
|
||||
"form-group-class":"st_force_mon_rows_input st_force_mon_rows_1",
|
||||
attribute:'localStorage="montage_use"',
|
||||
selector:'st_force_mon_rows',
|
||||
"description": "",
|
||||
"default": "0",
|
||||
"example": "",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
"placeholder": "#main_header{background:#b59f00}",
|
||||
"description": "",
|
||||
"default": "",
|
||||
"example": "",
|
||||
"possible": ""
|
||||
},
|
||||
{
|
||||
"field": lang['Browser Console Log'],
|
||||
|
@ -6637,7 +6647,7 @@ module.exports = function(s,config,lang){
|
|||
{
|
||||
"name": "ip",
|
||||
"field": lang['IP Address'],
|
||||
"description": lang[lang["fieldTextIp"]],
|
||||
"description": lang["fieldTextIp"],
|
||||
"example": "10.1.100.1-10.1.100.254",
|
||||
},
|
||||
{
|
||||
|
@ -8472,6 +8482,23 @@ module.exports = function(s,config,lang){
|
|||
"field": lang["Search Object Tags"],
|
||||
"example": "person",
|
||||
},
|
||||
{
|
||||
id:'videosTable_tag_search_andonly',
|
||||
field: lang['Search Type'],
|
||||
default: '0',
|
||||
"fieldType": "select",
|
||||
possible:[
|
||||
{
|
||||
"name": lang.OR,
|
||||
"value": "0",
|
||||
selected: true,
|
||||
},
|
||||
{
|
||||
"name": lang.AND,
|
||||
"value": "1"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "date_selector",
|
||||
"field": lang.Date,
|
||||
|
@ -8479,7 +8506,7 @@ module.exports = function(s,config,lang){
|
|||
{
|
||||
id:'videosTable_cloudVideos',
|
||||
field: lang['Video Set'],
|
||||
default:'local',
|
||||
default: 'local',
|
||||
"fieldType": "select",
|
||||
possible:[
|
||||
{
|
||||
|
@ -9211,6 +9238,13 @@ module.exports = function(s,config,lang){
|
|||
<div class="btn-group">
|
||||
<input class="form-control form-control-sm" id="timeline-video-object-search" placeholder="${lang['Search Object Tags']}">
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<select class="form-control form-control-sm" id="timeline-video-type">
|
||||
<option value="">${lang.Local}</option>
|
||||
<option value="cloud">${lang.Cloud}</option>
|
||||
<option value="archive">${lang.Archive}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-sm btn-primary" timeline-action="autoGridSizer" title="${lang.autoResizeGrid}"><i class="fa fa-expand"></i></a>
|
||||
<a class="btn btn-sm btn-primary" timeline-action="playUntilVideoEnd" title="${lang.playUntilVideoEnd}"><i class="fa fa-step-forward"></i></a>
|
||||
|
|
|
@ -20,6 +20,15 @@
|
|||
"Geolocation": "Geolocation",
|
||||
"Configure": "Configure",
|
||||
"Scan": "Scan",
|
||||
"See System Logs": "See System Logs",
|
||||
"Mount Manager": "Mount Manager",
|
||||
"mountManagerDescription": "Setup drive mounts here.",
|
||||
"Mount Added": "Mount Added",
|
||||
"mountAddedText": "Mount has been added to /etc/fstab. You may now use it as your default Videos directory.",
|
||||
"mountAddedBadCredentialsText": "Mount has been added to /etc/fstab but it seems credentials are incorrect.",
|
||||
"Failed to Remove Mount": "Failed to Remove Mount",
|
||||
"Failed to Add Mount": "Failed to Add Mount",
|
||||
"Add Mount": "Add Mount",
|
||||
"Rally": "Rally",
|
||||
"rallyApiKeyFieldText": "Permissions required : Get Monitors, Edit Monitors, View Streams, View Videos.",
|
||||
"rallyDescription": "Here you can connect to a separate Shinobi server and add their cameras to this Shinobi server. The connection would be directly from the separate server instead of directly from the cameras.",
|
||||
|
@ -66,10 +75,13 @@
|
|||
"How to Connect": "How to Connect",
|
||||
"Cycle": "Cycle",
|
||||
"Cycle Interval": "Cycle Interval",
|
||||
"Cycle Monitor Height": "Cycle Monitor Height",
|
||||
"Number of Cycle Monitors": "Number of Cycle Monitors",
|
||||
"Rows and Columns": "Rows and Columns",
|
||||
"Number of Monitors": "Number of Monitors",
|
||||
"Number of Rows": "Number of Rows",
|
||||
"Number of Columns": "Number of Columns",
|
||||
"Cycle Monitors": "Cycle Monitors",
|
||||
"Cycle Monitors per row": "Cycle Monitors per row",
|
||||
"General": "General",
|
||||
"Login": "Login",
|
||||
"Room ID": "Room ID",
|
||||
"Substream": "Substream",
|
||||
|
@ -127,6 +139,11 @@
|
|||
"Google Drive": "Google Drive",
|
||||
"Invert Y-Axis": "Invert Y-Axis",
|
||||
"Get Code": "Get Code",
|
||||
"Invalid Action": "Invalid Action",
|
||||
"Button Code": "Button Code",
|
||||
"PTZ Control ID": "PTZ Control ID",
|
||||
"ptzControlIdFieldText": "When using a Control Stick or GamePad you can choose an available number to use.",
|
||||
"ptzControlIdNotFound": "The selected PTZ Control ID was not assigned to a Monitor. Please add it to a Monitor via the Monitor Settings in the Identity section.",
|
||||
"PTZ Tracking": "PTZ Tracking",
|
||||
"PTZ Tracking Target": "PTZ Tracking Target",
|
||||
"Event Counts": "Event Counts",
|
||||
|
@ -156,6 +173,7 @@
|
|||
"Open All Monitors": "Open All Monitors",
|
||||
"Open Wall Display": "Open Wall Display",
|
||||
"New Wall Display": "New Wall Display",
|
||||
"Wall Display Settings": "Wall Display Settings",
|
||||
"openWallViewInfo": "Open Monitors in the top right of this window.",
|
||||
"Accounts": "Accounts",
|
||||
"Settings": "Settings",
|
||||
|
@ -399,6 +417,7 @@
|
|||
"Action for Selected": "Action for Selected",
|
||||
"Selected": "Selected",
|
||||
"Search": "Search",
|
||||
"Search Type": "Search Type",
|
||||
"No": "No",
|
||||
"Yes": "Yes",
|
||||
"Start": "Start",
|
||||
|
@ -453,6 +472,7 @@
|
|||
"ONVIF Port": "ONVIF Port",
|
||||
"ONVIF Scanner": "ONVIF Scanner",
|
||||
"ONVIF Events": "ONVIF Events",
|
||||
"ONVIFNotEnabled": "ONVIF Not Enabled in Monitor Settings",
|
||||
"ONVIFEventsNotAvailable": "ONVIF Events not Available",
|
||||
"ONVIFEventsNotAvailableText1": "This service may not be available for this camera or ONVIF has not initialized yet.",
|
||||
"ONVIFnotCompliantProfileT": "Camera is not ONVIF Profile T Compliant",
|
||||
|
@ -533,6 +553,9 @@
|
|||
"Use Global Wasabi Hot Cloud Storage Video Storage": "Use Global Wasabi Hot Cloud Storage Video Storage",
|
||||
"Use Global Backblaze B2 Video Storage": "Use Global Backblaze B2 Video Storage",
|
||||
"Use Global WebDAV Video Storage": "Use Global WebDAV Video Storage",
|
||||
"windowsCantUseFeature": "Windows cannot use this feature",
|
||||
"Mount Point": "Mount Point",
|
||||
"Mounted Drive Storage": "Mounted Drive Storage",
|
||||
"S3-Based Network Storage": "S3-Based Network Storage",
|
||||
"Amazon S3 Upload Error": "Amazon S3 Upload Error",
|
||||
"Wasabi Hot Cloud Storage Upload Error": "Wasabi Hot Cloud Storage Upload Error",
|
||||
|
@ -545,7 +568,17 @@
|
|||
"URL": "URL",
|
||||
"Operating Hours": "Operating Hours",
|
||||
"Autosave": "Autosave",
|
||||
"Source": "Source",
|
||||
"Delete Mount": "Delete Mount",
|
||||
"Mount Path": "Mount Path",
|
||||
"Mount Type": "Mount Type",
|
||||
"setVideosDirWarning": "Doing this while Monitors are recording may cause issues. Stop them before making this change.",
|
||||
"Save Directory": "Save Directory",
|
||||
"Path Inside": "Path Inside",
|
||||
"Path Inside Mount": "Path Inside Mount",
|
||||
"New Videos Directory Set": "New Videos Directory Set",
|
||||
"Use Default Videos Directory": "Use Default Videos Directory",
|
||||
"Set New Videos Directory": "Set New Videos Directory?",
|
||||
"CSS": "CSS <small>Style your dashboard.</small>",
|
||||
"Don't Stretch Monitors": "Don't Stretch Monitors",
|
||||
"Force Monitors Per Row": "Force Monitors Per Row",
|
||||
|
@ -1960,5 +1993,8 @@
|
|||
"saveUnknownFacesFieldText": "Save Unknown faces to the Face Manager. Manual sorting may still be required.",
|
||||
"Current Version": "Current Version",
|
||||
"Default is Global value": "Default is Global value",
|
||||
"rejectUnauth": "Ignore server certificate"
|
||||
"rejectUnauth": "Ignore server certificate",
|
||||
"Central Management" : "Central Management",
|
||||
"centralManagementSaved" : "Settings saved. Restarting connection to Central Managment Server.",
|
||||
"centralManagementNotEnabled" : "Management Server connectivity is not enabled. Activate your installation and add <code>\"enableMgmtConnect\": true</code> to your conf.json."
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -136,7 +136,7 @@ module.exports = (processCwd,config) => {
|
|||
}
|
||||
return theRequester(requestUrl,requestOptions)
|
||||
}
|
||||
const checkSubscription = (subscriptionId,callback) => {
|
||||
const checkSubscription = (subscriptionId,callback,suppressCheckNotice = false) => {
|
||||
function subscriptionFailed(){
|
||||
console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!')
|
||||
console.error('This Install of Shinobi is NOT Activated')
|
||||
|
@ -163,9 +163,11 @@ module.exports = (processCwd,config) => {
|
|||
}
|
||||
callback(hasSubcribed)
|
||||
if(hasSubcribed){
|
||||
s.systemLog('This Install of Shinobi is Activated')
|
||||
if(!json.expired && json.timeExpires){
|
||||
s.systemLog(`This License expires on ${json.timeExpires}`)
|
||||
if(!suppressCheckNotice){
|
||||
s.systemLog('This Install of Shinobi is Activated')
|
||||
if(!json.expired && json.timeExpires){
|
||||
s.systemLog(`This License expires on ${json.timeExpires}`)
|
||||
}
|
||||
}
|
||||
}else{
|
||||
subscriptionFailed()
|
||||
|
@ -187,6 +189,18 @@ module.exports = (processCwd,config) => {
|
|||
callback(hasSubcribed)
|
||||
}
|
||||
}
|
||||
function checkAgainSubscription(){
|
||||
let checkCount = 1
|
||||
return setInterval(function(){
|
||||
if(checkCount === 28){
|
||||
checkSubscription(config.subscriptionId || config.peerConnectKey || config.p2pApiKey, function(hasSubcribed){
|
||||
config.userHasSubscribed = hasSubcribed
|
||||
}, true);
|
||||
checkCount = 1;
|
||||
}
|
||||
++checkCount;
|
||||
}, 1000 * 60 * 60 * 24);
|
||||
}
|
||||
function isEven(value) {
|
||||
if (value%2 == 0)
|
||||
return true;
|
||||
|
@ -272,6 +286,7 @@ module.exports = (processCwd,config) => {
|
|||
localToUtc: localToUtc,
|
||||
formattedTime: formattedTime,
|
||||
checkSubscription: checkSubscription,
|
||||
checkAgainSubscription,
|
||||
isEven: isEven,
|
||||
fetchTimeout: fetchTimeout,
|
||||
fetchDownloadAndWrite: fetchDownloadAndWrite,
|
||||
|
|
|
@ -12,7 +12,7 @@ module.exports = (s,config,lang) => {
|
|||
const cameraCountChecks = [
|
||||
{ kind: 'ec2', maxCameras: 2, condition: config.isEC2 },
|
||||
{ kind: 'highCoreCount', maxCameras: 50, condition: config.isHighCoreCount },
|
||||
{ kind: 'default', maxCameras: 30, condition: true },
|
||||
{ kind: 'default', maxCameras: 15, condition: true },
|
||||
];
|
||||
if (!config.userHasSubscribed) {
|
||||
const monitorCountOnSystem = getTotalMonitorCount();
|
||||
|
|
|
@ -131,7 +131,9 @@ module.exports = function(s,config,lang,app){
|
|||
runningWorker = startWorker()
|
||||
}
|
||||
if(config.p2pEnabled){
|
||||
beginConnection()
|
||||
s.onLoadedUsersAtStartup(() => {
|
||||
beginConnection()
|
||||
})
|
||||
}
|
||||
/**
|
||||
* API : Superuser : Save P2P Server choice
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
const fs = require('fs').promises
|
||||
const { Worker } = require('worker_threads')
|
||||
const testMode = process.argv[2] === 'test'
|
||||
let passedJSON = false
|
||||
let passedConfig = {}
|
||||
const moduleName = 'connectToManagementServer'
|
||||
module.exports = (s,config,lang,app) => {
|
||||
if(!config.enableMgmtConnect){
|
||||
return;
|
||||
}
|
||||
const { modifyConfiguration, getConfiguration } = require('../system/utils.js')(config)
|
||||
require('./libs/centralConnect.js')(s,config,lang)
|
||||
require('./libs/pairServer.js')(s,config)
|
||||
|
||||
/**
|
||||
* API : Superuser : Save Management Server Settings
|
||||
*/
|
||||
app.post(config.webPaths.superApiPrefix+':auth/mgmt/save', function (req,res){
|
||||
s.superAuth(req.params,async (resp) => {
|
||||
// for saving :
|
||||
// form.peerConnectKey
|
||||
// form.managementServer
|
||||
const response = {ok: true};
|
||||
const form = s.getPostData(req,'data',true);
|
||||
config = Object.assign(config,form)
|
||||
const currentConfig = await getConfiguration()
|
||||
const configError = await modifyConfiguration(Object.assign(currentConfig,form))
|
||||
if(configError)s.systemLog(configError)
|
||||
try{
|
||||
s.restartCentralManagement()
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
s.closeJsonResponse(res,response)
|
||||
},res,req)
|
||||
})
|
||||
|
||||
/**
|
||||
* API : Delete Management Server Settings
|
||||
*/
|
||||
app.post(config.webPaths.superApiPrefix+':auth/mgmt/disconnect', async function (req,res){
|
||||
s.superAuth(req.params,async (resp) => {
|
||||
const response = {ok: true};
|
||||
const peerConnectKey = s.getPostData(req,'peerConnectKey');
|
||||
const currentConfig = await getConfiguration()
|
||||
if(currentConfig.peerConnectKey === peerConnectKey){
|
||||
delete(config.managementServer);
|
||||
delete(currentConfig.managementServer);
|
||||
const configError = await modifyConfiguration(currentConfig);
|
||||
if(configError)s.systemLog(configError);
|
||||
try{
|
||||
s.restartCentralManagement()
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
}else{
|
||||
response.ok = false;
|
||||
response.msg = 'Peer Connect Key not matching! Cannot disconnect.';
|
||||
}
|
||||
s.closeJsonResponse(res,response)
|
||||
},res,req)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
const { Worker } = require('worker_threads')
|
||||
const fs = require('fs').promises;
|
||||
module.exports = (s,config,lang) => {
|
||||
const {
|
||||
modifyConfiguration,
|
||||
} = require('../../system/utils.js')(config)
|
||||
const {
|
||||
getConnectionDetails,
|
||||
} = require('./connectDetails.js')(s,config)
|
||||
const configPath = process.cwd() + '/conf.json'
|
||||
async function startWorker(){
|
||||
if(!config.userHasSubscribed){
|
||||
return console.log(lang.centralManagementNotEnabled)
|
||||
}
|
||||
const configFromFile = JSON.parse(await fs.readFile(configPath, 'utf8'))
|
||||
configFromFile.timezone = config.timezone;
|
||||
console.log('Central Worker Starting...')
|
||||
const worker = new Worker(`${__dirname}/centralConnect/index.js`, {
|
||||
workerData: {
|
||||
config: configFromFile,
|
||||
}
|
||||
});
|
||||
worker.on('message', (data) => {
|
||||
switch(data.f){
|
||||
case'connectDetailsRequest':
|
||||
getConnectionDetails().then((connectDetails) => {
|
||||
worker.postMessage({ f: 'connectDetails', connectDetails })
|
||||
}).catch((error) => {
|
||||
console.error('FAILED TO GET connectDetails',error)
|
||||
worker.postMessage({ f: 'connectDetails', connectDetails: {} })
|
||||
})
|
||||
break;
|
||||
case'modifyConfiguration':
|
||||
console.log('Editing Configuration...', data.data.form)
|
||||
modifyConfiguration(data.data.form)
|
||||
break;
|
||||
case'restart':
|
||||
s.systemLog('Restarting Central Connection...')
|
||||
worker.terminate()
|
||||
break;
|
||||
}
|
||||
});
|
||||
worker.on('error', (err) => {
|
||||
console.error('cameraPeer Error', err)
|
||||
});
|
||||
worker.on('exit', (code) => {
|
||||
console.log('cameraPeer Exited, Restarting...', code)
|
||||
startWorker()
|
||||
});
|
||||
s.centralManagementWorker = worker;
|
||||
}
|
||||
s.onLoadedUsersAtStartup(() => {
|
||||
startWorker()
|
||||
})
|
||||
s.restartCentralManagement = () => {
|
||||
if(!s.centralManagementWorker){
|
||||
startWorker()
|
||||
}else{
|
||||
s.centralManagementWorker.terminate()
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,366 @@
|
|||
const { spawn } = require('child_process');
|
||||
const { parentPort, workerData } = require('worker_threads');
|
||||
process.on("uncaughtException", function(error) {
|
||||
console.error(error);
|
||||
});
|
||||
const activeTerminalCommands = {}
|
||||
let config = workerData.config
|
||||
let lang = workerData.lang
|
||||
let sslInfo = config.ssl || {}
|
||||
const expectedConfigPath = `./conf.json`
|
||||
const hostPeerServer = config.managementServer;
|
||||
const peerConnectKey = config.peerConnectKey;
|
||||
if(!peerConnectKey || !hostPeerServer){
|
||||
console.log(`Management Server Connection Not Configured!`)
|
||||
setInterval(() => {
|
||||
|
||||
}, 1000 * 60 * 60 * 24)
|
||||
return;
|
||||
}
|
||||
const fs = require("fs").promises
|
||||
const net = require("net")
|
||||
const bson = require('bson')
|
||||
const WebSocket = require('cws')
|
||||
const os = require('os');
|
||||
const { EventEmitter } = require('node:events');
|
||||
const internalEvents = new EventEmitter();
|
||||
const s = {
|
||||
debugLog: () => {},
|
||||
systemLog: (...args) => {
|
||||
parentPort.postMessage({
|
||||
f: 'systemLog',
|
||||
data: args
|
||||
})
|
||||
},
|
||||
}
|
||||
console.log('hostPeerServer',hostPeerServer)
|
||||
if(config.debugLog){
|
||||
s.debugLog = (...args) => {
|
||||
parentPort.postMessage({
|
||||
f: 'debugLog',
|
||||
data: args
|
||||
})
|
||||
}
|
||||
}
|
||||
parentPort.on('message',(data) => {
|
||||
switch(data.f){
|
||||
case'init':
|
||||
initialize()
|
||||
break;
|
||||
case'connectDetails':
|
||||
data.connectDetails.peerConnectKey = peerConnectKey;
|
||||
internalEvents.emit('connectDetails', data.connectDetails);
|
||||
// outboundMessage('connectDetailsForManagement', data.connectDetails, '1');
|
||||
break;
|
||||
case'exit':
|
||||
s.debugLog('Closing Central Connection...')
|
||||
process.exit(0)
|
||||
break;
|
||||
}
|
||||
})
|
||||
let outboundMessage = null
|
||||
var socketCheckTimer = null
|
||||
var heartbeatTimer = null
|
||||
var heartBeatCheckTimout = null
|
||||
var onClosedTimeout = null
|
||||
let stayDisconnected = false
|
||||
const requestConnections = {}
|
||||
const requestConnectionsData = {}
|
||||
function getServerIPAddresses() {
|
||||
const interfaces = os.networkInterfaces();
|
||||
const addresses = [];
|
||||
for (let interfaceName in interfaces) {
|
||||
for (let i = 0; i < interfaces[interfaceName].length; i++) {
|
||||
const iface = interfaces[interfaceName][i];
|
||||
if (iface.family === 'IPv4' && !iface.internal) {
|
||||
addresses.push(iface.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
function getRequestConnection(requestId){
|
||||
return requestConnections[requestId] || {
|
||||
write: () => {}
|
||||
}
|
||||
}
|
||||
function clearAllTimeouts(){
|
||||
clearInterval(heartbeatTimer)
|
||||
clearTimeout(heartBeatCheckTimout)
|
||||
clearTimeout(onClosedTimeout)
|
||||
}
|
||||
function getConnectionDetails(){
|
||||
return new Promise((resolve) => {
|
||||
internalEvents.once('connectDetails' ,(data) => {
|
||||
resolve(data)
|
||||
})
|
||||
parentPort.postMessage({ f: 'connectDetailsRequest' })
|
||||
})
|
||||
}
|
||||
function requestConnectionDetails(){
|
||||
}
|
||||
function startConnection(){
|
||||
let tunnelToP2P
|
||||
stayDisconnected = false
|
||||
const allMessageHandlers = []
|
||||
async function startWebsocketConnection(key,callback){
|
||||
s.debugLog(`startWebsocketConnection EXECUTE`,new Error())
|
||||
console.log('Central : Connecting to Central Server...')
|
||||
function createWebsocketConnection(){
|
||||
clearAllTimeouts()
|
||||
return new Promise((resolve,reject) => {
|
||||
try{
|
||||
stayDisconnected = true
|
||||
if(tunnelToP2P)tunnelToP2P.close()
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
tunnelToP2P = new WebSocket(hostPeerServer);
|
||||
stayDisconnected = false;
|
||||
tunnelToP2P.on('open', function(){
|
||||
resolve(tunnelToP2P)
|
||||
})
|
||||
tunnelToP2P.on('error', (err) => {
|
||||
console.log(`Central tunnelToCentral Error : `,err)
|
||||
console.log(`Central Restarting...`)
|
||||
// disconnectedConnection()
|
||||
})
|
||||
tunnelToP2P.on('close', () => {
|
||||
console.log(`Central Connection Closed!`)
|
||||
clearAllTimeouts()
|
||||
// onClosedTimeout = setTimeout(() => {
|
||||
// disconnectedConnection();
|
||||
// },5000)
|
||||
});
|
||||
tunnelToP2P.onmessage = function(event){
|
||||
const data = bson.deserialize(Buffer.from(event.data))
|
||||
allMessageHandlers.forEach((handler) => {
|
||||
if(data.f === handler.key){
|
||||
handler.callback(data.data,data.rid)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
clearInterval(socketCheckTimer)
|
||||
socketCheckTimer = setInterval(() => {
|
||||
// s.debugLog('Tunnel Ready State :',tunnelToP2P.readyState)
|
||||
if(tunnelToP2P.readyState !== 1){
|
||||
s.debugLog('Tunnel NOT Ready! Reconnecting...')
|
||||
disconnectedConnection()
|
||||
}
|
||||
},1000 * 20)
|
||||
})
|
||||
}
|
||||
function disconnectedConnection(code,reason){
|
||||
s.debugLog('stayDisconnected',stayDisconnected)
|
||||
clearAllTimeouts()
|
||||
s.debugLog('DISCONNECTED!')
|
||||
if(stayDisconnected)return;
|
||||
s.debugLog('RESTARTING!')
|
||||
setTimeout(() => {
|
||||
if(tunnelToP2P && tunnelToP2P.readyState !== 1)startWebsocketConnection()
|
||||
},2000)
|
||||
}
|
||||
s.debugLog(hostPeerServer)
|
||||
await createWebsocketConnection(hostPeerServer,allMessageHandlers)
|
||||
console.log('Central : Connected! Authenticating...')
|
||||
const connectDetails = await getConnectionDetails()
|
||||
sendDataToTunnel({
|
||||
isShinobi: !!config.passwordType,
|
||||
peerConnectKey,
|
||||
connectDetails,
|
||||
ipAddresses: getServerIPAddresses(),
|
||||
config: JSON.parse(await fs.readFile(expectedConfigPath,'utf8')),
|
||||
})
|
||||
clearInterval(heartbeatTimer)
|
||||
heartbeatTimer = setInterval(() => {
|
||||
sendDataToTunnel({
|
||||
f: 'ping',
|
||||
})
|
||||
}, 1000 * 10)
|
||||
setTimeout(() => {
|
||||
if(tunnelToP2P.readyState !== 1)refreshHeartBeatCheck()
|
||||
},5000)
|
||||
}
|
||||
function sendDataToTunnel(data){
|
||||
tunnelToP2P.send(bson.serialize(data))
|
||||
}
|
||||
startWebsocketConnection()
|
||||
function onIncomingMessage(key,callback){
|
||||
allMessageHandlers.push({
|
||||
key: key,
|
||||
callback: callback,
|
||||
})
|
||||
}
|
||||
outboundMessage = (key,data,requestId) => {
|
||||
sendDataToTunnel({
|
||||
f: key,
|
||||
data: data,
|
||||
rid: requestId
|
||||
})
|
||||
}
|
||||
async function createRemoteSocket(host,port,requestId,initData){
|
||||
// if(requestConnections[requestId]){
|
||||
// remotesocket.off('data')
|
||||
// remotesocket.off('drain')
|
||||
// remotesocket.off('close')
|
||||
// requestConnections[requestId].end()
|
||||
// }
|
||||
const responseTunnel = await getResponseTunnel(requestId)
|
||||
let remotesocket = new net.Socket();
|
||||
remotesocket.on('ready',() => {
|
||||
remotesocket.write(initData.buffer)
|
||||
})
|
||||
remotesocket.on('error',(err) => {
|
||||
s.debugLog('createRemoteSocket ERROR',err)
|
||||
})
|
||||
remotesocket.on('data', function(data) {
|
||||
requestConnectionsData[requestId] = data.toString()
|
||||
responseTunnel.send('data',data)
|
||||
})
|
||||
remotesocket.on('drain', function() {
|
||||
responseTunnel.send('resume',{})
|
||||
});
|
||||
remotesocket.on('close', function() {
|
||||
delete(requestConnectionsData[requestId])
|
||||
responseTunnel.send('end',{})
|
||||
setTimeout(() => {
|
||||
if(
|
||||
responseTunnel &&
|
||||
(responseTunnel.readyState === 0 || responseTunnel.readyState === 1)
|
||||
){
|
||||
responseTunnel.close()
|
||||
}
|
||||
},5000)
|
||||
});
|
||||
remotesocket.connect(port, host || 'localhost');
|
||||
requestConnections[requestId] = remotesocket
|
||||
return remotesocket
|
||||
}
|
||||
function writeToServer(data,requestId){
|
||||
var flushed = getRequestConnection(requestId).write(data.buffer)
|
||||
if (!flushed) {
|
||||
outboundMessage('pause',{},requestId)
|
||||
}
|
||||
}
|
||||
function refreshHeartBeatCheck(){
|
||||
clearTimeout(heartBeatCheckTimout)
|
||||
heartBeatCheckTimout = setTimeout(() => {
|
||||
startWebsocketConnection()
|
||||
},1000 * 10 * 1.5)
|
||||
}
|
||||
onIncomingMessage('connect',async (data,requestId) => {
|
||||
s.debugLog('New Request Incoming', 'localhost', config.port, requestId);
|
||||
const socket = await createRemoteSocket('localhost', config.port, requestId, data.init)
|
||||
})
|
||||
onIncomingMessage('data',writeToServer)
|
||||
|
||||
onIncomingMessage('resume',function(data,requestId){
|
||||
requestConnections[requestId].resume()
|
||||
})
|
||||
onIncomingMessage('pause',function(data,requestId){
|
||||
requestConnections[requestId].pause()
|
||||
})
|
||||
onIncomingMessage('pong',function(data,requestId){
|
||||
refreshHeartBeatCheck()
|
||||
})
|
||||
onIncomingMessage('init',function(data,requestId){
|
||||
console.log(`Central : Authenticated!`)
|
||||
})
|
||||
onIncomingMessage('modifyConfiguration',function(data,requestId){
|
||||
parentPort.postMessage({
|
||||
f: 'modifyConfiguration',
|
||||
data: data
|
||||
})
|
||||
})
|
||||
onIncomingMessage('getConfiguration',function(data, requestId){
|
||||
outboundMessage('getConfigurationResponse', Object.assign({}, config), requestId)
|
||||
})
|
||||
onIncomingMessage('restart',function(data,requestId){
|
||||
parentPort.postMessage({ f: 'restart' })
|
||||
})
|
||||
onIncomingMessage('end',function(data,requestId){
|
||||
try{
|
||||
requestConnections[requestId].end()
|
||||
}catch(err){
|
||||
s.debugLog(`Reqest Failed to END ${requestId}`)
|
||||
s.debugLog(`Failed Request ${requestConnectionsData[requestId]}`)
|
||||
delete(requestConnectionsData[requestId])
|
||||
s.debugLog(err)
|
||||
// console.log('requestConnections',requestConnections)
|
||||
}
|
||||
})
|
||||
onIncomingMessage('disconnect',function(data,requestId){
|
||||
console.log(`FAILED LICENSE CHECK ON P2P`)
|
||||
const retryLater = data && data.retryLater;
|
||||
stayDisconnected = !retryLater
|
||||
if(retryLater)console.log(`Retrying Central Later...`)
|
||||
})
|
||||
}
|
||||
const responseTunnels = {}
|
||||
async function getResponseTunnel(originalRequestId){
|
||||
return responseTunnels[originalRequestId] || await createResponseTunnel(originalRequestId)
|
||||
}
|
||||
function createResponseTunnel(originalRequestId){
|
||||
const responseTunnelMessageHandlers = []
|
||||
function onMessage(key,callback){
|
||||
responseTunnelMessageHandlers.push({
|
||||
key: key,
|
||||
callback: callback,
|
||||
})
|
||||
}
|
||||
return new Promise((resolve,reject) => {
|
||||
const responseTunnel = new WebSocket(hostPeerServer);
|
||||
function sendToResponseTunnel(data){
|
||||
responseTunnel.send(
|
||||
bson.serialize(data)
|
||||
)
|
||||
}
|
||||
function sendData(key,data){
|
||||
sendToResponseTunnel({
|
||||
f: key,
|
||||
data: data,
|
||||
rid: originalRequestId
|
||||
})
|
||||
}
|
||||
responseTunnel.on('error', (err) => {
|
||||
s.debugLog('responseTunnel ERROR',err)
|
||||
})
|
||||
responseTunnel.on('open', function(){
|
||||
sendToResponseTunnel({
|
||||
responseTunnel: originalRequestId,
|
||||
peerConnectKey,
|
||||
})
|
||||
})
|
||||
responseTunnel.on('close', function(){
|
||||
delete(responseTunnels[originalRequestId])
|
||||
})
|
||||
onMessage('ready', function(){
|
||||
const finalData = {
|
||||
onMessage,
|
||||
send: sendData,
|
||||
sendRaw: sendToResponseTunnel,
|
||||
close: responseTunnel.close
|
||||
}
|
||||
responseTunnels[originalRequestId] = finalData;
|
||||
resolve(finalData)
|
||||
})
|
||||
responseTunnel.onmessage = function(event){
|
||||
const data = bson.deserialize(Buffer.from(event.data))
|
||||
responseTunnelMessageHandlers.forEach((handler) => {
|
||||
if(data.f === handler.key){
|
||||
handler.callback(data.data,data.rid)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
function closeResponseTunnel(originalRequestId){
|
||||
// also should be handled server side
|
||||
try{
|
||||
responseTunnels[originalRequestId].close()
|
||||
}catch(err){
|
||||
s.debugLog('closeResponseTunnel',err)
|
||||
}
|
||||
}
|
||||
startConnection()
|
|
@ -0,0 +1,109 @@
|
|||
const fs = require("fs").promises
|
||||
module.exports = (s,config) => {
|
||||
const configPath = config.thisIsDocker ? "/config/super.json" : s.location.super;
|
||||
async function generateSuperUserJson(){
|
||||
const baseConfig = [
|
||||
{
|
||||
"mail": `${s.gid(6)}@${s.gid(6)}.${s.gid(3)}`,
|
||||
"pass": s.gid(32),
|
||||
"tokens": [
|
||||
s.gid(30)
|
||||
]
|
||||
}
|
||||
];
|
||||
await fs.writeFile(configPath, JSON.stringify(baseConfig,null,3))
|
||||
return baseConfig[0]
|
||||
}
|
||||
async function getFirstSuperUser(){
|
||||
let superUser = JSON.parse(await fs.readFile(configPath))[0]
|
||||
if(!superUser){
|
||||
superUser = await generateSuperUserJson()
|
||||
}
|
||||
if(!superUser.tokens || !superUser.tokens[0]){
|
||||
const newToken = await applySuperApiKey()
|
||||
superUser.tokens = [newToken]
|
||||
}
|
||||
return superUser
|
||||
}
|
||||
async function applySuperApiKey(){
|
||||
const superUserList = JSON.parse(await fs.readFile(configPath))
|
||||
const newToken = s.gid(30)
|
||||
superUserList[0].tokens = [newToken]
|
||||
await fs.writeFile(configPath, JSON.stringify(superUserList,null,3))
|
||||
return newToken;
|
||||
}
|
||||
async function getFirstSuperApiKey(){
|
||||
const superUser = await getFirstSuperUser()
|
||||
const apiKey = superUser.tokens[0];
|
||||
return apiKey
|
||||
}
|
||||
async function getFirstAdminApiKey(){
|
||||
const { rows } = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Users",
|
||||
limit: 1,
|
||||
});
|
||||
const user = rows[0];
|
||||
const apiKey = await getFirstApiKey(user.ke, user.uid)
|
||||
return { groupKey: user.ke, apiKey }
|
||||
}
|
||||
async function getFirstApiKey(groupKey, userId){
|
||||
const { rows } = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "API",
|
||||
where: { ke: groupKey, uid: userId }
|
||||
});
|
||||
let suitableKey = null;
|
||||
for(row of rows){
|
||||
row.details = JSON.parse(row.details)
|
||||
const detailValues = Object.values(row.details);
|
||||
const theFiltered = detailValues.filter(item => item != 1);
|
||||
if(theFiltered.length === 0){
|
||||
suitableKey = row.code
|
||||
}
|
||||
};
|
||||
if(!suitableKey){
|
||||
suitableKey = await createApiKey(groupKey, userId)
|
||||
}
|
||||
return suitableKey;
|
||||
}
|
||||
async function createApiKey(groupKey, userId){
|
||||
const newApiKey = s.gid(30);
|
||||
await s.knexQueryPromise({
|
||||
action: "insert",
|
||||
table: "API",
|
||||
insert: {
|
||||
ke : groupKey,
|
||||
uid : userId,
|
||||
code : newApiKey,
|
||||
ip : '0.0.0.0',
|
||||
details : s.stringJSON({
|
||||
"auth_socket": "1",
|
||||
"get_monitors": "1",
|
||||
"edit_monitors": "1",
|
||||
"control_monitors": "1",
|
||||
"get_logs": "1",
|
||||
"watch_stream": "1",
|
||||
"watch_snapshot": "1",
|
||||
"watch_videos": "1",
|
||||
"delete_videos": "1"
|
||||
})
|
||||
}
|
||||
});
|
||||
return newApiKey
|
||||
}
|
||||
async function getConnectionDetails(){
|
||||
const superApiKey = await getFirstSuperApiKey()
|
||||
const { groupKey, apiKey } = await getFirstAdminApiKey()
|
||||
return {
|
||||
superApiKey,
|
||||
groupKey,
|
||||
apiKey,
|
||||
}
|
||||
}
|
||||
return {
|
||||
getConnectionDetails
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
const http = require('http');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
var cors = require('cors');
|
||||
var bodyParser = require('body-parser');
|
||||
module.exports = (s,config) => {
|
||||
const { modifyConfiguration, getConfiguration } = require('../../system/utils.js')(config)
|
||||
const pairPort = config.pairPort || 8091
|
||||
const bindIp = config.bindip
|
||||
const server = http.createServer(app);
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({extended: true}));
|
||||
app.use(cors());
|
||||
|
||||
server.listen(pairPort, bindIp, function(){
|
||||
console.log('Management Pair Server Listening on '+pairPort);
|
||||
});
|
||||
|
||||
/**
|
||||
* API : Superuser : Save Management Server Settings
|
||||
*/
|
||||
app.post('/mgmt/connect', async function (req,res){
|
||||
// form.managementServer
|
||||
// Example of Shinobi and MGMT on same server
|
||||
// ws://127.0.0.1:8663
|
||||
const response = {ok: true};
|
||||
if(!config.managementServer){
|
||||
const managementServer = s.getPostData(req,'managementServer');
|
||||
const peerConnectKey = s.getPostData(req,'peerConnectKey');
|
||||
if(peerConnectKey){
|
||||
config = Object.assign(config, { managementServer, peerConnectKey })
|
||||
const currentConfig = await getConfiguration()
|
||||
if(peerConnectKey){
|
||||
currentConfig.peerConnectKey = peerConnectKey
|
||||
}
|
||||
// else if(!currentConfig.peerConnectKey){
|
||||
// currentConfig.peerConnectKey = `bihan${s.gid(20)}`
|
||||
// }
|
||||
const configError = await modifyConfiguration(Object.assign(currentConfig, { managementServer }))
|
||||
if(configError)s.systemLog(configError)
|
||||
try{
|
||||
s.centralManagementWorker.terminate()
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
}else{
|
||||
response.ok = false;
|
||||
response.msg = 'No P2P API Key Provided';
|
||||
}
|
||||
}else{
|
||||
response.ok = false;
|
||||
response.msg = 'Already Configured';
|
||||
}
|
||||
s.closeJsonResponse(res,response)
|
||||
})
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
function prettyPrint(obj){
|
||||
return JSON.stringify(obj,null,3)
|
||||
}
|
||||
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 parseJSON(string){
|
||||
var parsed
|
||||
try{
|
||||
parsed = JSON.parse(string)
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
if(!parsed)parsed = string
|
||||
return parsed
|
||||
}
|
||||
module.exports = {
|
||||
parseJSON,
|
||||
prettyPrint,
|
||||
generateId,
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
const WebSocket = require('cws');
|
||||
function createWebSocketServer(options){
|
||||
const theWebSocket = new WebSocket.Server(options ? options : {
|
||||
noServer: true
|
||||
});
|
||||
theWebSocket.broadcast = function(data) {
|
||||
theWebSocket.clients.forEach((client) => {
|
||||
try{
|
||||
client.sendData(data)
|
||||
}catch(err){
|
||||
// console.log(err)
|
||||
}
|
||||
})
|
||||
};
|
||||
return theWebSocket
|
||||
}
|
||||
function createWebSocketClient(connectionHost,options){
|
||||
const clientConnection = new WebSocket(connectionHost, options.engineOptions);
|
||||
if(options.onMessage){
|
||||
const onMessage = options.onMessage;
|
||||
clientConnection.on('message', message => {
|
||||
const data = JSON.parse(message);
|
||||
onMessage(data);
|
||||
});
|
||||
}
|
||||
return clientConnection
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createWebSocketServer,
|
||||
createWebSocketClient,
|
||||
}
|
|
@ -137,14 +137,16 @@ module.exports = function(s,config,lang,app,io){
|
|||
if(onvifOptions.mid && onvifOptions.ke){
|
||||
const groupKey = onvifOptions.ke
|
||||
const monitorId = onvifOptions.mid
|
||||
const theDevice = s.group[groupKey].activeMonitors[monitorId].onvifConnection
|
||||
theUrl = addCredentialsToUrl({
|
||||
username: onvifOptions.username,
|
||||
password: onvifOptions.password,
|
||||
url: (await theDevice.services.media.getSnapshotUri({
|
||||
ProfileToken : theDevice.current_profile.token,
|
||||
})).GetSnapshotUriResponse.MediaUri.Uri
|
||||
});
|
||||
const theDevice = s.group[groupKey].activeMonitors[monitorId].onvifConnection || (await s.createOnvifDevice({ id: e.mid, ke: e.ke })).device;
|
||||
if(theDevice){
|
||||
theUrl = addCredentialsToUrl({
|
||||
username: theDevice.user,
|
||||
password: theDevice.pass,
|
||||
url: (await theDevice.services.media.getSnapshotUri({
|
||||
ProfileToken : theDevice.current_profile.token,
|
||||
})).data.GetSnapshotUriResponse.MediaUri.Uri
|
||||
});
|
||||
}
|
||||
}else{
|
||||
theUrl = addCredentialsToStreamLink({
|
||||
username: onvifOptions.username,
|
||||
|
|
|
@ -283,11 +283,13 @@ module.exports = async (s,config,lang,app,io) => {
|
|||
if(config.language === rule){
|
||||
lang = Object.assign(lang,fileData)
|
||||
}
|
||||
if(s.loadedLanguages[rule]){
|
||||
s.loadedLanguages[rule] = Object.assign(s.loadedLanguages[rule],fileData)
|
||||
}else{
|
||||
s.loadedLanguages[rule] = Object.assign(s.copySystemDefaultLanguage(),fileData)
|
||||
}
|
||||
if(!s.languageModifications[rule])s.languageModifications[rule] = [];
|
||||
s.languageModifications[rule].push(fileData);
|
||||
// if(s.loadedLanguages[rule]){
|
||||
// s.loadedLanguages[rule] = Object.assign(s.loadedLanguages[rule],fileData)
|
||||
// }else{
|
||||
// s.loadedLanguages[rule] = Object.assign(s.copySystemDefaultLanguage(),fileData)
|
||||
// }
|
||||
})
|
||||
})
|
||||
break;
|
||||
|
@ -345,6 +347,7 @@ module.exports = async (s,config,lang,app,io) => {
|
|||
LibsCss: [],
|
||||
AssetsJs: [],
|
||||
AssetsCss: [],
|
||||
superPageTabs: [],
|
||||
superPageBlocks: [],
|
||||
superLibsJs: [],
|
||||
superRawJs: [],
|
||||
|
|
|
@ -251,7 +251,9 @@ module.exports = function(s,config,lang,app,io){
|
|||
var firstDroppedPart = pathPieces[2]
|
||||
var monitorEventDropDir = s.dir.dropInEvents + ke + '/' + mid + '/'
|
||||
var deleteKey = monitorEventDropDir + firstDroppedPart
|
||||
onFileOrFolderFound(monitorEventDropDir + firstDroppedPart,deleteKey,Object.assign({},s.group[ke].rawMonitorConfigurations[mid]))
|
||||
fs.mkdir(pathPieces.join('/'), { recursive: true }, (err) => {
|
||||
onFileOrFolderFound(monitorEventDropDir + firstDroppedPart,deleteKey,Object.assign({},s.group[ke].rawMonitorConfigurations[mid]))
|
||||
})
|
||||
})
|
||||
resolve({root: s.dir.dropInEvents + user.ke})
|
||||
}else{
|
||||
|
|
|
@ -43,12 +43,15 @@ module.exports = function (s, config, lang) {
|
|||
|
||||
return new Cam(options, function (error) {
|
||||
if (error) {
|
||||
onvifEventLog(`ONVIF Event Error`,e)
|
||||
onvifEventLog(`ONVIF Event Error`,error)
|
||||
return
|
||||
}
|
||||
this.on('event', function (event) {
|
||||
handleEvent(event, monitorConfig, onvifEventLog);
|
||||
})
|
||||
this.on('eventsError', function (e) {
|
||||
onvifEventLog(`ONVIF Event Error`,e)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
|
|
@ -583,7 +583,7 @@ module.exports = (s,config,lang) => {
|
|||
monitorDetails.detector_buffer_acodec !== 'no' &&
|
||||
monitorDetails.detector_buffer_acodec !== 'auto'
|
||||
){
|
||||
outputMap += `-map 0:1 `
|
||||
outputMap += `-map 0:1? `
|
||||
}
|
||||
const secondsBefore = parseInt(monitorDetails.detector_buffer_seconds_before) || 5
|
||||
let LiveStartIndex = parseInt(secondsBefore / 2 + 1)
|
||||
|
@ -611,6 +611,7 @@ module.exports = (s,config,lang) => {
|
|||
const secondBefore = (parseInt(monitorDetails.detector_buffer_seconds_before) || 5) + 1
|
||||
s.insertCompletedVideo(monitorConfig,{
|
||||
file : filename,
|
||||
objects: overlappingRecordings && d.details && d.details.matrices instanceof Array ? d.details.matrices : undefined,
|
||||
endTime: moment(new Date()).subtract(secondBefore,'seconds')._d,
|
||||
},function(err,response){
|
||||
const autoCompressionEnabled = monitorDetails.auto_compress_videos === '1';
|
||||
|
|
|
@ -75,6 +75,7 @@ module.exports = function(s,config){
|
|||
///////// SYSTEM ////////
|
||||
createExtension(`onProcessReady`)
|
||||
createExtension(`onProcessExit`)
|
||||
createExtension(`onLoadedUsersAtStartup`)
|
||||
createExtension(`onBeforeDatabaseLoad`)
|
||||
createExtension(`onFFmpegLoaded`)
|
||||
createExtension(`beforeMonitorsLoadedOnStartup`)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
var fs = require('fs')
|
||||
module.exports = function(s,config){
|
||||
s.languageModifications = {};
|
||||
if(!config.language){
|
||||
config.language='en_CA'
|
||||
}
|
||||
|
@ -8,6 +9,11 @@ module.exports = function(s,config){
|
|||
let gotLang = {}
|
||||
try{
|
||||
eval(`gotLang = ${fs.readFileSync(s.location.languages+'/'+choice+'.json','utf8')}`)
|
||||
if(s.languageModifications[choice]){
|
||||
for(mods of s.languageModifications[choice]){
|
||||
Object.assign(gotLang, mods)
|
||||
}
|
||||
}
|
||||
}catch(er){
|
||||
console.error(er)
|
||||
console.log('There was an error loading your language file.')
|
||||
|
@ -34,6 +40,11 @@ module.exports = function(s,config){
|
|||
s.getLanguageFile = function(rule){
|
||||
if(rule && rule !== ''){
|
||||
var file = s.loadedLanguages[file]
|
||||
// if(s.languageModifications[file]){
|
||||
// for(mods of s.languageModifications[file]){
|
||||
// Object.assign(file, mods)
|
||||
// }
|
||||
// }
|
||||
s.debugLog(file)
|
||||
if(!file){
|
||||
try{
|
||||
|
|
|
@ -497,7 +497,8 @@ module.exports = function(s,config,lang){
|
|||
s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
||||
}
|
||||
}
|
||||
if(s.group[e.ke].activeMonitors[e.mid].onvifConnection){
|
||||
const onvifDevice = s.group[e.ke].activeMonitors[e.mid].onvifConnection || (await s.createOnvifDevice({ id: e.mid, ke: e.ke })).device;
|
||||
if(onvifDevice){
|
||||
try{
|
||||
const screenShot = await s.getSnapshotFromOnvif({
|
||||
ke: e.ke,
|
||||
|
@ -733,6 +734,13 @@ module.exports = function(s,config,lang){
|
|||
break;
|
||||
case'start':case'record':
|
||||
await monitorStart(e)
|
||||
s.tx({
|
||||
f: 'monitor_watch_on',
|
||||
id: monitorId,
|
||||
ke: groupKey,
|
||||
subStreamChannel: s.group[groupKey].activeMonitors[monitorId].subStreamChannel,
|
||||
warnings: s.group[groupKey].activeMonitors[monitorId].warnings || []
|
||||
}, `MON_${groupKey}${monitorId}`)
|
||||
break;
|
||||
default:
|
||||
console.log('No s.camera execute : ',selectedMode)
|
||||
|
|
|
@ -220,7 +220,7 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
const temporaryImageFile = streamDir + s.gid(5) + '.jpg'
|
||||
const ffmpegCmd = splitForFFMPEG(`-y -loglevel warning -re ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f mjpeg -an -frames:v 1 "${temporaryImageFile}"`)
|
||||
const snapProcess = spawn('ffmpeg',ffmpegCmd,{detached: true})
|
||||
const snapProcess = spawn(config.ffmpegDir, ffmpegCmd, {detached: true})
|
||||
snapProcess.stderr.on('data',function(data){
|
||||
// s.debugLog(data.toString())
|
||||
})
|
||||
|
@ -447,8 +447,22 @@ module.exports = (s,config,lang) => {
|
|||
ffmpegProcess.stdio[pipeNumber].pipe(activeMonitor.mp4frag[pipeNumber],{ end: false })
|
||||
break;
|
||||
case'mjpeg':
|
||||
frameToStreamAdded = function (d) {
|
||||
activeMonitor.emitterChannel[pipeNumber].emit('data', d)
|
||||
}
|
||||
break;
|
||||
case'b64':
|
||||
var buffer
|
||||
frameToStreamAdded = function(d){
|
||||
activeMonitor.emitterChannel[pipeNumber].emit('data',d)
|
||||
if(!buffer){
|
||||
buffer=[d]
|
||||
}else{
|
||||
buffer.push(d)
|
||||
}
|
||||
if((d[d.length-2] === 0xFF && d[d.length-1] === 0xD9)){
|
||||
activeMonitor.emitterChannel[pipeNumber].emit('data',Buffer.concat(buffer))
|
||||
buffer = null
|
||||
}
|
||||
}
|
||||
break;
|
||||
case'flv':
|
||||
|
@ -1034,19 +1048,47 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
}, 1000 * creationInterval * 2);
|
||||
}
|
||||
function setUpChosenDetector(e){
|
||||
const groupKey = e.ke
|
||||
const monitorId = e.mid || e.id
|
||||
const activeMonitor = getActiveMonitor(groupKey,monitorId);
|
||||
const monitorConfig = getMonitorConfiguration(groupKey,monitorId);
|
||||
const monitorDetails = monitorConfig.details;
|
||||
let chosenDetector = monitorDetails.detectors_selected;
|
||||
if(chosenDetector instanceof Array)chosenDetector = chosenDetector.join(',');
|
||||
let sendToDetector = (data) => {
|
||||
s.ocvTx({
|
||||
f : 'frame',
|
||||
mon : monitorDetails,
|
||||
ke : groupKey,
|
||||
id : monitorId,
|
||||
time : s.formattedTime(),
|
||||
frame : data
|
||||
})
|
||||
}
|
||||
if(chosenDetector && !(chosenDetector.includes('all'))){
|
||||
const pluginsGettingIt = chosenDetector.split(',').map(item => item.trim()).filter(item => !!item);
|
||||
sendToDetector = (data) => {
|
||||
for(pluginName of pluginsGettingIt){
|
||||
s.sendToDetector(pluginName, {
|
||||
f : 'frame',
|
||||
mon : monitorDetails,
|
||||
ke : groupKey,
|
||||
id : monitorId,
|
||||
time : s.formattedTime(),
|
||||
frame : data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
activeMonitor.forDetectorJpegOutputAlone = sendToDetector;
|
||||
}
|
||||
function onDetectorJpegOutputAlone(e,d){
|
||||
if(s.isAtleatOneDetectorPluginConnected){
|
||||
const groupKey = e.ke
|
||||
const monitorId = e.mid || e.id
|
||||
const monitorConfig = getMonitorConfiguration(groupKey,monitorId);
|
||||
s.ocvTx({
|
||||
f: 'frame',
|
||||
mon: monitorConfig.details,
|
||||
ke: groupKey,
|
||||
id: monitorId,
|
||||
time: s.formattedTime(),
|
||||
frame: d
|
||||
})
|
||||
const activeMonitor = getActiveMonitor(groupKey,monitorId);
|
||||
activeMonitor.forDetectorJpegOutputAlone(d)
|
||||
}
|
||||
}
|
||||
function onDetectorJpegOutputSecondary(e,buffer){
|
||||
|
@ -1213,6 +1255,7 @@ module.exports = (s,config,lang) => {
|
|||
onDetectorJpegOutputSecondary(e,data)
|
||||
})
|
||||
}else{
|
||||
setUpChosenDetector(e)
|
||||
activeMonitor.spawn.stdio[4].on('data',function(data){
|
||||
onDetectorJpegOutputAlone(e,data)
|
||||
})
|
||||
|
@ -1222,6 +1265,7 @@ module.exports = (s,config,lang) => {
|
|||
onDetectorJpegOutputSecondary(e,data)
|
||||
})
|
||||
}else{
|
||||
setUpChosenDetector(e)
|
||||
activeMonitor.spawn.stdio[4].on('data',function(data){
|
||||
onDetectorJpegOutputAlone(e,data)
|
||||
})
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
const path = require('path')
|
||||
module.exports = (s,config,lang,app,io) => {
|
||||
// for unix-based systems only (has /etc/fstab)
|
||||
if(s.isWin){
|
||||
app.all([
|
||||
'list',
|
||||
'mount',
|
||||
'removeMount',
|
||||
'setVideosDir',
|
||||
].map(item => `${config.webPaths.superApiPrefix}:auth/mountManager/${item}`), function (req,res){
|
||||
s.closeJsonResponse(res, {
|
||||
ok: false,
|
||||
msg: lang.windowsCantUseFeature,
|
||||
error: lang.windowsCantUseFeature
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
const {
|
||||
modifyConfiguration,
|
||||
} = require('./system/utils.js')(config)
|
||||
const {
|
||||
mount,
|
||||
update,
|
||||
remove,
|
||||
list,
|
||||
remountAll,
|
||||
remount,
|
||||
unmount,
|
||||
createMountPoint,
|
||||
checkDiskPathExists,
|
||||
} = require('node-fstab');
|
||||
/**
|
||||
* API : Remount All in fstab
|
||||
*/
|
||||
app.get(config.webPaths.superApiPrefix+':auth/mountManager/list', function (req,res){
|
||||
s.superAuth(req.params, async (resp) => {
|
||||
const response = await list();
|
||||
s.closeJsonResponse(res, response);
|
||||
},res,req);
|
||||
});
|
||||
/**
|
||||
* API : Add Mount to fstab
|
||||
*/
|
||||
app.post(config.webPaths.superApiPrefix+':auth/mountManager/mount', function (req,res){
|
||||
s.superAuth(req.params, async (resp) => {
|
||||
const { sourceTarget, localPath, mountType, options } = req.body;
|
||||
const response = { ok: false }
|
||||
if(sourceTarget && localPath){
|
||||
try{
|
||||
const createDirResponse = await createMountPoint(localPath)
|
||||
response.createDirResponse = createDirResponse
|
||||
}catch(err){
|
||||
console.error(err)
|
||||
}
|
||||
try{
|
||||
const { exists } = await checkDiskPathExists(localPath)
|
||||
if(exists){
|
||||
const unmountResponse = await unmount(localPath)
|
||||
response.unmountResponse = unmountResponse
|
||||
}
|
||||
}catch(err){
|
||||
console.error(err)
|
||||
}
|
||||
const updateResponse = await update(sourceTarget, localPath, mountType, options);
|
||||
response.updateResponse = updateResponse
|
||||
response.ok = updateResponse.ok
|
||||
const remountResponse = await remount(localPath)
|
||||
response.remountResponse = remountResponse
|
||||
if(!remountResponse.ok){
|
||||
await remove(localPath);
|
||||
response.ok = false;
|
||||
response.error = remountResponse.error;
|
||||
}
|
||||
response.mount = {
|
||||
device: sourceTarget,
|
||||
mountPoint: localPath,
|
||||
type: mountType,
|
||||
options,
|
||||
}
|
||||
}else{
|
||||
response.error = lang['Invalid Data']
|
||||
}
|
||||
s.closeJsonResponse(res, response);
|
||||
},res,req);
|
||||
});
|
||||
/**
|
||||
* API : Remove Mount to fstab
|
||||
*/
|
||||
app.post(config.webPaths.superApiPrefix+':auth/mountManager/removeMount', function (req,res){
|
||||
s.superAuth(req.params, async (resp) => {
|
||||
const { localPath } = req.body;
|
||||
try{
|
||||
await unmount(localPath)
|
||||
if(config.videosDir.startsWith(localPath)){
|
||||
const configError = await modifyConfiguration({
|
||||
videosDir: '__DIR__/videos',
|
||||
}, true);
|
||||
}
|
||||
}catch(err){
|
||||
console.error(err)
|
||||
}
|
||||
const response = await remove(localPath);
|
||||
s.closeJsonResponse(res, response);
|
||||
},res,req);
|
||||
});
|
||||
/**
|
||||
* API : Set Mount Point as Videos Directory (videosDir)
|
||||
*/
|
||||
app.post(config.webPaths.superApiPrefix+':auth/mountManager/setVideosDir', function (req,res){
|
||||
s.superAuth(req.params, async (resp) => {
|
||||
const { localPath, pathInside } = req.body;
|
||||
const isDefaultDir = localPath === '__DIR__/videos';
|
||||
const response = { ok: false }
|
||||
try{
|
||||
const { exists } = isDefaultDir ? { exists: true } : await checkDiskPathExists(localPath)
|
||||
if(exists){
|
||||
const newVideosDirPath = pathInside ? path.join(localPath, pathInside) : localPath;
|
||||
const createDirResponse = isDefaultDir ? true : await createMountPoint(newVideosDirPath)
|
||||
const configError = await modifyConfiguration({
|
||||
videosDir: newVideosDirPath,
|
||||
}, true);
|
||||
response.ok = true;
|
||||
response.configError = configError;
|
||||
response.createDirResponse = createDirResponse;
|
||||
}
|
||||
}catch(err){
|
||||
console.error(err)
|
||||
}
|
||||
s.closeJsonResponse(res, response);
|
||||
},res,req);
|
||||
});
|
||||
}
|
|
@ -383,7 +383,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
/**
|
||||
* API : Get List of Connected Plugins
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/plugins/list', async (req,res) => {
|
||||
app.get(config.webPaths.apiPrefix+':auth/plugins/:ke/list', async (req,res) => {
|
||||
s.auth(req.params, async (resp) => {
|
||||
s.closeJsonResponse(res,{
|
||||
ok: true,
|
||||
|
|
123
libs/socketio.js
123
libs/socketio.js
|
@ -336,6 +336,7 @@ module.exports = function(s,config,lang,io){
|
|||
tx({f:'users_online',users:s.group[d.ke].users})
|
||||
s.tx({f:'user_status_change',ke:d.ke,uid:cn.uid,status:1,user:s.group[d.ke].users[d.auth]},'GRP_'+d.ke)
|
||||
s.sendDiskUsedAmountToClients(d.ke)
|
||||
s.sendCloudDiskUsedAmountToClients(d.ke)
|
||||
tx({
|
||||
f:'init_success',
|
||||
users:s.group[d.ke].vid,
|
||||
|
@ -728,81 +729,77 @@ module.exports = function(s,config,lang,io){
|
|||
// super page socket functions
|
||||
cn.on('super',function(d){
|
||||
if(!cn.init&&d.f=='init'){
|
||||
d.ok=s.superAuth({mail:d.mail,pass:d.pass},function(data){
|
||||
cn.mail=d.mail
|
||||
d.ok=s.superAuth(d.auth ? { auth: d.auth } : {mail:d.mail,pass:d.pass},function(data){
|
||||
cn.mail = data.$user.mail
|
||||
cn.join('$');
|
||||
var tempSessionKey = s.gid(30)
|
||||
var tempSessionKey = d.auth || s.gid(30)
|
||||
cn.superSessionKey = tempSessionKey
|
||||
s.superUsersApi[tempSessionKey] = data
|
||||
s.superUsersApi[tempSessionKey].cnid = cn.id
|
||||
if(!data.$user.tokens)data.$user.tokens = {}
|
||||
data.$user.tokens[tempSessionKey] = {}
|
||||
cn.ip=cn.request.connection.remoteAddress
|
||||
s.userLog({ke:'$',mid:'$USER'},{type:lang['Websocket Connected'],msg:{for:lang['Superuser'],id:cn.mail,ip:cn.ip}})
|
||||
cn.init='super';
|
||||
s.tx({f:'init_success',mail:d.mail,superSessionKey:tempSessionKey},cn.id);
|
||||
s.tx({f:'init_success',mail:cn.mail,superSessionKey:tempSessionKey},cn.id);
|
||||
})
|
||||
if(d.ok===false){
|
||||
cn.disconnect();
|
||||
}
|
||||
}else{
|
||||
if(cn.mail&&cn.init=='super'){
|
||||
switch(d.f){
|
||||
case'logs':
|
||||
switch(d.ff){
|
||||
case'delete':
|
||||
s.knexQuery({
|
||||
action: "delete",
|
||||
table: "Logs",
|
||||
where: {
|
||||
ke: d.ke,
|
||||
}
|
||||
})
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case'system':
|
||||
switch(d.ff){
|
||||
case'update':
|
||||
}else if(cn.mail&&cn.init=='super'){
|
||||
switch(d.f){
|
||||
case'logs':
|
||||
switch(d.ff){
|
||||
case'delete':
|
||||
s.knexQuery({
|
||||
action: "delete",
|
||||
table: "Logs",
|
||||
where: {
|
||||
ke: d.ke,
|
||||
}
|
||||
})
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case'system':
|
||||
switch(d.ff){
|
||||
case'update':
|
||||
s.ffmpegKill()
|
||||
s.systemLog('Shinobi ordered to update',{
|
||||
by:cn.mail,
|
||||
ip:cn.ip
|
||||
})
|
||||
var updateProcess = spawn('sh',(s.mainDirectory+'/UPDATE.sh').split(' '),{detached: true})
|
||||
updateProcess.stderr.on('data',function(data){
|
||||
s.systemLog('Update Info',data.toString())
|
||||
})
|
||||
updateProcess.stdout.on('data',function(data){
|
||||
s.systemLog('Update Info',data.toString())
|
||||
})
|
||||
break;
|
||||
case'restart':
|
||||
//config.webPaths.superApiPrefix+':auth/restart/:script'
|
||||
d.check=function(x){return d.target.indexOf(x)>-1}
|
||||
if(d.check('system')){
|
||||
s.systemLog('Shinobi ordered to restart',{by:cn.mail,ip:cn.ip})
|
||||
s.ffmpegKill()
|
||||
s.systemLog('Shinobi ordered to update',{
|
||||
by:cn.mail,
|
||||
ip:cn.ip
|
||||
})
|
||||
var updateProcess = spawn('sh',(s.mainDirectory+'/UPDATE.sh').split(' '),{detached: true})
|
||||
updateProcess.stderr.on('data',function(data){
|
||||
s.systemLog('Update Info',data.toString())
|
||||
})
|
||||
updateProcess.stdout.on('data',function(data){
|
||||
s.systemLog('Update Info',data.toString())
|
||||
})
|
||||
break;
|
||||
case'restart':
|
||||
//config.webPaths.superApiPrefix+':auth/restart/:script'
|
||||
d.check=function(x){return d.target.indexOf(x)>-1}
|
||||
if(d.check('system')){
|
||||
s.systemLog('Shinobi ordered to restart',{by:cn.mail,ip:cn.ip})
|
||||
s.ffmpegKill()
|
||||
exec('pm2 restart '+s.mainDirectory+'/camera.js')
|
||||
}
|
||||
if(d.check('cron')){
|
||||
s.systemLog('Shinobi CRON ordered to restart',{by:cn.mail,ip:cn.ip})
|
||||
exec('pm2 restart '+s.mainDirectory+'/cron.js')
|
||||
}
|
||||
if(d.check('logs')){
|
||||
s.systemLog('Flush PM2 Logs',{by:cn.mail,ip:cn.ip})
|
||||
exec('pm2 flush')
|
||||
}
|
||||
break;
|
||||
case'configure':
|
||||
s.systemLog('conf.json Modified',{by:cn.mail,ip:cn.ip,old:jsonfile.readFileSync(s.location.config)})
|
||||
jsonfile.writeFile(s.location.config,d.data,{spaces: 2},function(){
|
||||
s.tx({f:'save_configuration'},cn.id)
|
||||
})
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
exec('pm2 restart '+s.mainDirectory+'/camera.js')
|
||||
}
|
||||
if(d.check('cron')){
|
||||
s.systemLog('Shinobi CRON ordered to restart',{by:cn.mail,ip:cn.ip})
|
||||
exec('pm2 restart '+s.mainDirectory+'/cron.js')
|
||||
}
|
||||
if(d.check('logs')){
|
||||
s.systemLog('Flush PM2 Logs',{by:cn.mail,ip:cn.ip})
|
||||
exec('pm2 flush')
|
||||
}
|
||||
break;
|
||||
case'configure':
|
||||
s.systemLog('conf.json Modified',{by:cn.mail,ip:cn.ip,old:jsonfile.readFileSync(s.location.config)})
|
||||
jsonfile.writeFile(s.location.config,d.data,{spaces: 2},function(){
|
||||
s.tx({f:'save_configuration'},cn.id)
|
||||
})
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -3,6 +3,7 @@ module.exports = (s,config,databaseOptions) => {
|
|||
var databaseOptions = {
|
||||
client: config.databaseType,
|
||||
connection: config.db,
|
||||
pool: { min: 0, max: 10, propagateCreateError: true }
|
||||
}
|
||||
if(databaseOptions.client.indexOf('sqlite')>-1){
|
||||
databaseOptions.client = 'sqlite3';
|
||||
|
|
|
@ -9,7 +9,8 @@ module.exports = function(s,config,lang,io){
|
|||
scanForOrphanedVideos
|
||||
} = require('./video/utils.js')(s,config,lang)
|
||||
const {
|
||||
checkSubscription
|
||||
checkSubscription,
|
||||
checkAgainSubscription,
|
||||
} = require('./basic/utils.js')(process.cwd(),config)
|
||||
const {
|
||||
checkForStaticUsers
|
||||
|
@ -60,7 +61,7 @@ module.exports = function(s,config,lang,io){
|
|||
table: "Monitors",
|
||||
},function(err,monitors) {
|
||||
foundMonitors = monitors
|
||||
if(err){s.systemLog(err)}
|
||||
if(err){s.systemLog('Startup Error', err.toString())}
|
||||
if(monitors && monitors[0]){
|
||||
var didNotLoad = 0
|
||||
var loadCompleted = 0
|
||||
|
@ -89,7 +90,7 @@ module.exports = function(s,config,lang,io){
|
|||
});
|
||||
const monObj = Object.assign({},monitor,{id : monitor.mid})
|
||||
await s.camera('stop',monObj);
|
||||
await s.camera(monitor.mode,monObj);
|
||||
if(!config.safeMode)await s.camera(monitor.mode,monObj);
|
||||
checkAnother()
|
||||
},1000)
|
||||
}else{
|
||||
|
@ -120,11 +121,12 @@ module.exports = function(s,config,lang,io){
|
|||
if(orphanedFilesCount){
|
||||
orphanedVideosForMonitors[monitor.ke][monitor.mid] += orphanedFilesCount
|
||||
}
|
||||
if(orphanedVideosForMonitors[monitor.ke][monitor.mid] == 0)delete(orphanedVideosForMonitors[monitor.ke][monitor.mid]);
|
||||
++loadCompleted
|
||||
if(monitors[loadCompleted]){
|
||||
await checkForOrphanedVideosForMonitor(monitors[loadCompleted])
|
||||
}else{
|
||||
s.systemLog(lang.startUpText6+' : '+s.s(orphanedVideosForMonitors))
|
||||
s.systemLog(lang.startUpText6, s.s(orphanedVideosForMonitors))
|
||||
delete(foundMonitors)
|
||||
callback()
|
||||
}
|
||||
|
@ -135,7 +137,7 @@ module.exports = function(s,config,lang,io){
|
|||
}
|
||||
}
|
||||
var loadDiskUseForUser = function(user,callback){
|
||||
s.systemLog(user.mail+' : '+lang.startUpText0)
|
||||
s.systemLog(lang.startUpText0, user.mail)
|
||||
var userDetails = JSON.parse(user.details)
|
||||
s.group[user.ke].sizeLimit = parseFloat(userDetails.size) || 10000
|
||||
s.group[user.ke].sizeLimitVideoPercent = parseFloat(userDetails.size_video_percent) || 90
|
||||
|
@ -248,7 +250,7 @@ module.exports = function(s,config,lang,io){
|
|||
})
|
||||
s.cloudDisksLoaded.forEach(function(storageType){
|
||||
var firstCount = user.cloudDiskUse[storageType].firstCount
|
||||
s.systemLog(user.mail+' : '+lang.startUpText1+' : '+firstCount,storageType,user.cloudDiskUse[storageType].usedSpace)
|
||||
// s.systemLog(lang.startUpText1, user.mail+' : '+firstCount,storageType,user.cloudDiskUse[storageType].usedSpace)
|
||||
delete(user.cloudDiskUse[storageType].firstCount)
|
||||
})
|
||||
}
|
||||
|
@ -343,7 +345,7 @@ module.exports = function(s,config,lang,io){
|
|||
storageIndex.usedSpaceVideos = usedSpaceVideos / 1048576
|
||||
storageIndex.usedSpaceFilebin = usedSpaceFilebin / 1048576
|
||||
storageIndex.usedSpaceTimelapseFrames = usedSpaceTimelapseFrames / 1048576
|
||||
s.systemLog(user.mail+' : '+path+' : '+videos.length,storageIndex.usedSpace)
|
||||
// s.systemLog(user.mail+' : '+path+' : '+videos.length,storageIndex.usedSpace)
|
||||
++currentStorageNumber
|
||||
readStorageArray()
|
||||
}
|
||||
|
@ -420,12 +422,13 @@ module.exports = function(s,config,lang,io){
|
|||
setTimeout(async () => {
|
||||
await checkForStaticUsers()
|
||||
//check for subscription
|
||||
checkSubscription(config.subscriptionId,function(hasSubcribed){
|
||||
checkSubscription(config.subscriptionId || config.peerConnectKey || config.p2pApiKey, function(hasSubcribed){
|
||||
config.userHasSubscribed = hasSubcribed
|
||||
//check terminal commander
|
||||
checkForTerminalCommands(function(){
|
||||
//load administrators (groups)
|
||||
loadAdminUsers(function(){
|
||||
s.runExtensionsForArray('onLoadedUsersAtStartup', null, [])
|
||||
//load monitors (for groups)
|
||||
loadMonitors(function(){
|
||||
//check for orphaned videos
|
||||
|
@ -436,7 +439,8 @@ module.exports = function(s,config,lang,io){
|
|||
})
|
||||
})
|
||||
})
|
||||
},1500)
|
||||
},1500);
|
||||
s.subscriptionIntervalCheck = checkAgainSubscription();
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
const fs = require('fs');
|
||||
const spawn = require('child_process').spawn;
|
||||
const {
|
||||
mergeDeep,
|
||||
} = require('../common.js')
|
||||
module.exports = (config) => {
|
||||
var currentlyUpdating = false
|
||||
return {
|
||||
|
@ -11,13 +14,15 @@ module.exports = (config) => {
|
|||
"Shinobi": s.currentVersion,
|
||||
"Node.js": process.version,
|
||||
"FFmpeg": s.ffmpegVersion,
|
||||
"isActivated": config.userHasSubscribed
|
||||
"isActivated": config.userHasSubscribed,
|
||||
"previousShinobi": s.versionsUsed,
|
||||
},
|
||||
Machine: {
|
||||
"CPU Core Count": s.coreCount,
|
||||
"Total RAM": s.totalmem,
|
||||
"Operating System Platform": s.platform,
|
||||
},
|
||||
|
||||
}
|
||||
if(s.expiryDate)response.Versions["License Expires On"] = s.expiryDate
|
||||
return response
|
||||
|
@ -31,13 +36,20 @@ module.exports = (config) => {
|
|||
});
|
||||
});
|
||||
},
|
||||
modifyConfiguration: (postBody) => {
|
||||
modifyConfiguration: (postBody, useBase) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(config)
|
||||
|
||||
const configPath = config.thisIsDocker ? "/config/conf.json" : s.location.config;
|
||||
const configData = JSON.stringify(postBody,null,3);
|
||||
|
||||
let configToPost = postBody;
|
||||
if(useBase){
|
||||
try{
|
||||
const configBase = s.parseJSON(fs.readFileSync(configPath),{});
|
||||
configToPost = mergeDeep(configBase, postBody)
|
||||
}catch(err){
|
||||
console.error('modifyConfiguration : Failed to use Config base!')
|
||||
}
|
||||
}
|
||||
const configData = JSON.stringify(configToPost, null, 3);
|
||||
fs.writeFile(configPath, configData, resolve);
|
||||
});
|
||||
},
|
||||
|
|
|
@ -8,6 +8,8 @@ module.exports = function(s,config,lang,app,io){
|
|||
backblazeB2: require('./uploaders/backblazeB2.js'),
|
||||
amazonS3: require('./uploaders/amazonS3.js'),
|
||||
webdav: require('./uploaders/webdav.js'),
|
||||
//local storage
|
||||
mnt: require('./uploaders/mount.js'),
|
||||
//oauth
|
||||
googleDrive: require('./uploaders/googleDrive.js'),
|
||||
//simple storage
|
||||
|
@ -15,7 +17,9 @@ module.exports = function(s,config,lang,app,io){
|
|||
}
|
||||
Object.keys(loadedLibraries).forEach((key) => {
|
||||
var loadedLib = loadedLibraries[key](s,config,lang,app,io)
|
||||
loadedLib.isFormGroupGroup = true
|
||||
s.definitions["Account Settings"].blocks["Uploaders"].info.push(loadedLib)
|
||||
if(loadedLib.info){
|
||||
loadedLib.isFormGroupGroup = true
|
||||
s.definitions["Account Settings"].blocks["Uploaders"].info.push(loadedLib)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -253,6 +253,7 @@ module.exports = function(s,config,lang){
|
|||
"evaluation": "details.use_aws_s3 !== '0'",
|
||||
"name": lang["Amazon S3"],
|
||||
"color": "forestgreen",
|
||||
"uploaderId": 's3',
|
||||
"info": [
|
||||
{
|
||||
"name": "detail=aws_s3_save",
|
||||
|
|
|
@ -206,6 +206,7 @@ module.exports = function(s,config,lang){
|
|||
"evaluation": "details.use_bb_b2 !== '0'",
|
||||
"name": lang["Backblaze B2"],
|
||||
"color": "forestgreen",
|
||||
"uploaderId": 'b2',
|
||||
"info": [
|
||||
{
|
||||
"name": "detail=bb_b2_save",
|
||||
|
|
|
@ -303,6 +303,7 @@ module.exports = (s,config,lang,app,io) => {
|
|||
"evaluation": "details.use_googd !== '0'",
|
||||
"name": lang["Google Drive"],
|
||||
"color": "forestgreen",
|
||||
"uploaderId": 'googd',
|
||||
"info": [
|
||||
{
|
||||
"name": "detail=googd_save",
|
||||
|
|
|
@ -0,0 +1,327 @@
|
|||
const fs = require('fs').promises;
|
||||
const { createReadStream } = require('fs');
|
||||
const path = require('path');
|
||||
const {
|
||||
writeReadStream,
|
||||
checkDiskPathExists,
|
||||
} = require('node-fstab');
|
||||
module.exports = function(s,config,lang){
|
||||
if(s.isWin){
|
||||
return {}
|
||||
}
|
||||
function constructFilePath(groupKey, filePath){
|
||||
return path.join(s.group[groupKey].init.mnt_path, filePath)
|
||||
}
|
||||
const deleteObject = async (groupKey, filePath) => {
|
||||
const fullPath = constructFilePath(groupKey, filePath)
|
||||
const response = { ok: true }
|
||||
try{
|
||||
await fs.rm(fullPath)
|
||||
}catch(err){
|
||||
response.ok = false;
|
||||
response.err = err.toString();
|
||||
}
|
||||
return response
|
||||
};
|
||||
const uploadObject = async (groupKey, { filePath, readStream }) => {
|
||||
const fullPath = constructFilePath(groupKey, filePath)
|
||||
return await writeReadStream(readStream, fullPath);
|
||||
};
|
||||
const getObject = async (groupKey, filePath) => {
|
||||
const fullPath = constructFilePath(groupKey, filePath)
|
||||
return createReadStream(fullPath)
|
||||
};
|
||||
function beforeAccountSave(d){
|
||||
//d = save event
|
||||
d.formDetails.mnt_use_global = d.d.mnt_use_global
|
||||
d.formDetails.use_mnt = d.d.use_mnt
|
||||
}
|
||||
function cloudDiskUseStartup(group,userDetails){
|
||||
group.cloudDiskUse['mnt'].name = 'Mounted Drive'
|
||||
group.cloudDiskUse['mnt'].sizeLimitCheck = (userDetails.use_mnt_size_limit === '1')
|
||||
if(!userDetails.mnt_size_limit || userDetails.mnt_size_limit === ''){
|
||||
group.cloudDiskUse['mnt'].sizeLimit = 10000
|
||||
}else{
|
||||
group.cloudDiskUse['mnt'].sizeLimit = parseFloat(userDetails.mnt_size_limit)
|
||||
}
|
||||
}
|
||||
function loadGroupApp(e){
|
||||
// e = user
|
||||
var userDetails = JSON.parse(e.details)
|
||||
if(userDetails.mnt_use_global === '1' && config.cloudUploaders && config.cloudUploaders.WasabiHotCloudStorage){
|
||||
userDetails = Object.assign(userDetails,config.cloudUploaders.mountedDrive)
|
||||
}
|
||||
//Mounted Drive Storage
|
||||
if(
|
||||
!s.group[e.ke].mnt &&
|
||||
userDetails.mnt !== '0' &&
|
||||
userDetails.mnt_path
|
||||
){
|
||||
checkDiskPathExists(userDetails.mnt_path).then((response) => {
|
||||
if(response.exists){
|
||||
s.group[e.ke].mnt = userDetails.mnt_path;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
function unloadGroupApp(user){
|
||||
s.group[user.ke].mnt = null
|
||||
}
|
||||
function deleteVideo(e,video,callback){
|
||||
// e = user
|
||||
try{
|
||||
var videoDetails = JSON.parse(video.details)
|
||||
}catch(err){
|
||||
var videoDetails = video.details
|
||||
}
|
||||
if(video.type !== 'mnt'){
|
||||
callback()
|
||||
return
|
||||
}
|
||||
deleteObject(video.ke, videoDetails.location).then((response) => {
|
||||
if (response.err){
|
||||
console.error('Mounted Drive Storage DELETE Error')
|
||||
console.error(err);
|
||||
}
|
||||
callback()
|
||||
});
|
||||
}
|
||||
function onMonitorStart(monitorConfig){
|
||||
const groupKey = monitorConfig.ke;
|
||||
const monitorId = monitorConfig.mid;
|
||||
if(s.group[groupKey].mnt){
|
||||
const saveLocation = constructFilePath(groupKey, s.group[groupKey].init.mnt_dir + groupKey + '/' + monitorId);
|
||||
fs.mkdir(saveLocation, { recursive: true }).catch((err) => {
|
||||
console.error('Making Directory fail', err)
|
||||
});
|
||||
}
|
||||
}
|
||||
function uploadVideo(e,k,insertQuery){
|
||||
//e = video object
|
||||
//k = temporary values
|
||||
if(!k)k={};
|
||||
//cloud saver - Mounted Drive
|
||||
const groupKey = insertQuery.ke
|
||||
if(s.group[groupKey].mnt && s.group[groupKey].init.use_mnt !== '0' && s.group[groupKey].init.mnt_save === '1'){
|
||||
const filename = `${s.formattedTime(insertQuery.time)}.${insertQuery.ext}`
|
||||
var fileStream = createReadStream(k.dir + filename);
|
||||
var saveLocation = s.group[groupKey].init.mnt_dir+groupKey+'/'+e.mid+'/'+filename
|
||||
uploadObject(groupKey, {
|
||||
filePath: saveLocation,
|
||||
readStream: fileStream,
|
||||
}).then((response) => {
|
||||
if(response.err){
|
||||
s.userLog(e,{type:lang['Mounted Drive Storage Upload Error'],msg:response.err})
|
||||
}
|
||||
if(s.group[groupKey].init.mnt_log === '1' && response.ok){
|
||||
s.knexQuery({
|
||||
action: "insert",
|
||||
table: "Cloud Videos",
|
||||
insert: {
|
||||
mid: e.mid,
|
||||
ke: groupKey,
|
||||
ext: insertQuery.ext,
|
||||
time: insertQuery.time,
|
||||
status: 1,
|
||||
type : 'mnt',
|
||||
details: s.s({
|
||||
location : saveLocation
|
||||
}),
|
||||
size: k.filesize,
|
||||
end: k.endTime,
|
||||
href: ''
|
||||
}
|
||||
})
|
||||
s.setCloudDiskUsedForGroup(groupKey,{
|
||||
amount: k.filesizeMB,
|
||||
storageType: 'mnt'
|
||||
})
|
||||
s.purgeCloudDiskForGroup(e,'mnt')
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
function onInsertTimelapseFrame(monitorObject,queryInfo,filePath){
|
||||
var e = monitorObject
|
||||
if(s.group[e.ke].mnt && s.group[e.ke].init.use_mnt !== '0' && s.group[e.ke].init.mnt_save === '1'){
|
||||
var fileStream = createReadStream(filePath)
|
||||
fileStream.on('error', function (err) {
|
||||
console.error(err)
|
||||
})
|
||||
var saveLocation = s.group[e.ke].init.mnt_dir + e.ke + '/' + e.mid + '_timelapse/' + queryInfo.filename
|
||||
uploadObject(e.ke, {
|
||||
filePath: saveLocation,
|
||||
readStream: fileStream,
|
||||
}).then((response) => {
|
||||
if(response.err){
|
||||
s.userLog(e,{type:lang['Wasabi Hot Cloud Storage Upload Error'],msg:response.err})
|
||||
}
|
||||
if(s.group[e.ke].init.mnt_log === '1' && response.ok){
|
||||
s.knexQuery({
|
||||
action: "insert",
|
||||
table: "Cloud Timelapse Frames",
|
||||
insert: {
|
||||
mid: queryInfo.mid,
|
||||
ke: queryInfo.ke,
|
||||
time: queryInfo.time,
|
||||
filename: queryInfo.filename,
|
||||
type : 'mnt',
|
||||
details: s.s({
|
||||
location : saveLocation
|
||||
}),
|
||||
size: queryInfo.size,
|
||||
href: ''
|
||||
}
|
||||
})
|
||||
s.setCloudDiskUsedForGroup(e.ke,{
|
||||
amount : s.kilobyteToMegabyte(queryInfo.size),
|
||||
storageType : 'mnt'
|
||||
},'timelapseFrames')
|
||||
s.purgeCloudDiskForGroup(e,'mnt','timelapseFrames')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
function onDeleteTimelapseFrameFromCloud(e,frame,callback){
|
||||
// e = user
|
||||
try{
|
||||
var frameDetails = JSON.parse(frame.details)
|
||||
}catch(err){
|
||||
var frameDetails = frame.details
|
||||
}
|
||||
if(video.type !== 'mnt'){
|
||||
callback()
|
||||
return
|
||||
}
|
||||
if(!frameDetails.location){
|
||||
frameDetails.location = frame.href.split(locationUrl)[1]
|
||||
}
|
||||
deleteObject(e.ke, frameDetails.location).then((response) => {
|
||||
if (response.err){
|
||||
console.error('Mounted Drive Storage DELETE Error')
|
||||
console.error(err);
|
||||
}
|
||||
callback()
|
||||
});
|
||||
}
|
||||
async function onGetVideoData(video){
|
||||
const videoDetails = s.parseJSON(video.details)
|
||||
const saveLocation = videoDetails.location
|
||||
var fileStream = await getObject(video.ke, saveLocation);
|
||||
return fileStream
|
||||
}
|
||||
//Mounted Drive Storage
|
||||
s.addCloudUploader({
|
||||
name: 'mnt',
|
||||
loadGroupAppExtender: loadGroupApp,
|
||||
unloadGroupAppExtender: unloadGroupApp,
|
||||
insertCompletedVideoExtender: uploadVideo,
|
||||
deleteVideoFromCloudExtensions: deleteVideo,
|
||||
cloudDiskUseStartupExtensions: cloudDiskUseStartup,
|
||||
beforeAccountSave: beforeAccountSave,
|
||||
onAccountSave: cloudDiskUseStartup,
|
||||
onInsertTimelapseFrame: onInsertTimelapseFrame,
|
||||
onDeleteTimelapseFrameFromCloud: onDeleteTimelapseFrameFromCloud,
|
||||
onGetVideoData,
|
||||
});
|
||||
s.onMonitorStart(onMonitorStart);
|
||||
//return fields that will appear in settings
|
||||
return {
|
||||
"evaluation": "details.use_mnt !== '0'",
|
||||
"name": lang["Mounted Drive Storage"],
|
||||
"color": "forestgreen",
|
||||
"uploaderId": 'mnt',
|
||||
"info": [
|
||||
{
|
||||
"name": "detail=mnt_save",
|
||||
"selector":"autosave_mnt",
|
||||
"field": lang.Autosave,
|
||||
"description": "",
|
||||
"default": lang.No,
|
||||
"example": "",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hidden": true,
|
||||
"field": lang['Mount Point'],
|
||||
"name": "detail=mnt_path",
|
||||
"placeholder": "/mnt/yourdrive",
|
||||
"form-group-class": "autosave_mnt_input autosave_mnt_1",
|
||||
},
|
||||
{
|
||||
"hidden": true,
|
||||
"name": "detail=mnt_log",
|
||||
"field": lang['Save Links to Database'],
|
||||
"fieldType": "select",
|
||||
"selector": "h_mntsld",
|
||||
"form-group-class":"autosave_mnt_input autosave_mnt_1",
|
||||
"description": "",
|
||||
"default": "",
|
||||
"example": "",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hidden": true,
|
||||
"name": "detail=use_mnt_size_limit",
|
||||
"field": lang['Use Max Storage Amount'],
|
||||
"fieldType": "select",
|
||||
"selector": "h_mntzl",
|
||||
"form-group-class":"autosave_mnt_input autosave_mnt_1",
|
||||
"form-group-class-pre-layer":"h_mntsld_input h_mntsld_1",
|
||||
"description": "",
|
||||
"default": "",
|
||||
"example": "",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"hidden": true,
|
||||
"name": "detail=mnt_size_limit",
|
||||
"field": lang['Max Storage Amount'],
|
||||
"form-group-class":"autosave_mnt_input autosave_mnt_1",
|
||||
"form-group-class-pre-layer":"h_mntsld_input h_mntsld_1",
|
||||
"description": "",
|
||||
"default": "10000",
|
||||
"example": "",
|
||||
"possible": ""
|
||||
},
|
||||
{
|
||||
"hidden": true,
|
||||
"name": "detail=mnt_dir",
|
||||
"field": lang['Save Directory'],
|
||||
"form-group-class":"autosave_mnt_input autosave_mnt_1",
|
||||
"description": "",
|
||||
"default": "/",
|
||||
"example": "",
|
||||
"possible": ""
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
|
@ -255,6 +255,7 @@ module.exports = function(s,config,lang){
|
|||
"evaluation": "details.use_whcs !== '0'",
|
||||
"name": lang["S3-Based Network Storage"],
|
||||
"color": "forestgreen",
|
||||
"uploaderId": 'whcs',
|
||||
"info": [
|
||||
{
|
||||
"name": "detail=whcs_save",
|
||||
|
|
|
@ -94,6 +94,8 @@ module.exports = function(s,config,lang){
|
|||
"evaluation": "details.use_sftp !== '0'",
|
||||
"name": lang['SFTP (SSH File Transfer)'],
|
||||
"color": "forestgreen",
|
||||
"uploaderId": 'sftp',
|
||||
"simpleUploader": true,
|
||||
"info": [
|
||||
{
|
||||
"name": "detail=sftp_save",
|
||||
|
|
|
@ -239,6 +239,7 @@ module.exports = async function(s,config,lang){
|
|||
"evaluation": "details.use_webdav !== '0'",
|
||||
"name": lang.WebDAV,
|
||||
"color": "forestgreen",
|
||||
"uploaderId": 'webdav',
|
||||
"info": [
|
||||
{
|
||||
"name": "detail=webdav_save",
|
||||
|
|
13
libs/user.js
13
libs/user.js
|
@ -90,7 +90,16 @@ module.exports = function(s,config,lang){
|
|||
usedSpaceFilebin: s.group[groupKey].usedSpaceFilebin,
|
||||
usedSpaceTimelapseFrames: s.group[groupKey].usedSpaceTimelapseFrames,
|
||||
limit: s.group[groupKey].sizeLimit,
|
||||
addStorage: s.group[groupKey].addStorageUse
|
||||
addStorage: s.group[groupKey].addStorageUse,
|
||||
},'GRP_'+groupKey);
|
||||
}
|
||||
}
|
||||
s.sendCloudDiskUsedAmountToClients = function(groupKey){
|
||||
//send the amount used disk space to connected users
|
||||
if(s.group[groupKey]&&s.group[groupKey].init){
|
||||
s.tx({
|
||||
f: 'cloudDiskUsed',
|
||||
cloudDisks: s.group[groupKey].cloudDiskUse,
|
||||
},'GRP_'+groupKey);
|
||||
}
|
||||
}
|
||||
|
@ -151,6 +160,7 @@ module.exports = function(s,config,lang){
|
|||
theGroup.usedSpace = theGroup.usedSpace || ((e.size || 0) / 1048576)
|
||||
//emit the changes to connected users
|
||||
s.sendDiskUsedAmountToClients(e.ke)
|
||||
s.sendCloudDiskUsedAmountToClients(e.ke)
|
||||
// create monitor management queue
|
||||
theGroup.startMonitorInQueue = createQueueAwaited(0.5, 1)
|
||||
}
|
||||
|
@ -205,6 +215,7 @@ module.exports = function(s,config,lang){
|
|||
cloudDisk.usedSpaceVideos += amount
|
||||
break;
|
||||
}
|
||||
s.sendCloudDiskUsedAmountToClients(e.ke)
|
||||
})
|
||||
if(config.cron.deleteOverMax === true){
|
||||
s.group[e.ke].diskUsedEmitter.on('purgeCloud',function(storageType,storagePoint){
|
||||
|
|
|
@ -539,7 +539,28 @@ module.exports = (s,config,lang) => {
|
|||
})
|
||||
})
|
||||
}
|
||||
async function getAdminUser(groupKey, uid){
|
||||
const { rows } = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: "ke,uid,details,mail",
|
||||
table: "Users",
|
||||
where: [
|
||||
['uid','=', uid],
|
||||
['ke','=', groupKey],
|
||||
],
|
||||
limit: 1,
|
||||
});
|
||||
try{
|
||||
const user = rows[0];
|
||||
user.details = JSON.parse(user.details)
|
||||
return user
|
||||
}catch(err){
|
||||
s.systemLog(err)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return {
|
||||
getAdminUser,
|
||||
deleteSetOfVideos: deleteSetOfVideos,
|
||||
deleteSetOfTimelapseFrames: deleteSetOfTimelapseFrames,
|
||||
deleteSetOfFileBinFiles: deleteSetOfFileBinFiles,
|
||||
|
|
|
@ -1,5 +1,26 @@
|
|||
var exec = require('child_process').exec
|
||||
module.exports = function(s,config,lang,app,io){
|
||||
function getLastUsedCommits(numberOfCommits = 3){
|
||||
return new Promise((resolve) => {
|
||||
exec(`git log -${numberOfCommits} --pretty=format:"%H"`, function(err, response){
|
||||
try{
|
||||
if(err){
|
||||
console.error(err)
|
||||
resolve([])
|
||||
}else if(response){
|
||||
const commitIds = response.toString().split('\n');
|
||||
commitIds.shift();
|
||||
resolve(commitIds)
|
||||
}else{
|
||||
resolve([])
|
||||
}
|
||||
}catch(err){
|
||||
console.error(err)
|
||||
resolve([])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
var getRepositoryCommitId = function(callback){
|
||||
exec(`git rev-parse HEAD`,function(err,response){
|
||||
if(response){
|
||||
|
@ -8,7 +29,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
if(data.indexOf('not a git repository') === -1){
|
||||
s.currentVersion = data.trim()
|
||||
isGitRespository = true
|
||||
s.systemLog(`Current Version : ${s.currentVersion}`)
|
||||
s.systemLog(`Current Version`, s.currentVersion)
|
||||
}
|
||||
}else if(err){
|
||||
s.debugLog('Git is not installed.')
|
||||
|
@ -18,5 +39,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
}
|
||||
s.onProcessReady(async () => {
|
||||
getRepositoryCommitId()
|
||||
s.versionsUsed = await getLastUsedCommits(3);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -54,11 +54,8 @@ module.exports = (s,config,lang) => {
|
|||
})
|
||||
})
|
||||
}
|
||||
const scanForOrphanedVideos = (monitor,options) => {
|
||||
// const options = {
|
||||
// checkMax: 2
|
||||
// }
|
||||
options = options ? options : {}
|
||||
const scanForOrphanedVideos = (monitor, options) => {
|
||||
options = options || {}
|
||||
return new Promise((resolve,reject) => {
|
||||
const response = {ok: false}
|
||||
if(options.forceCheck === true || config.insertOrphans === true){
|
||||
|
@ -70,30 +67,32 @@ module.exports = (s,config,lang) => {
|
|||
let videosFound = 0;
|
||||
const videosDirectory = s.getVideoDirectory(monitor)
|
||||
const tempDirectory = s.getStreamsDirectory(monitor)
|
||||
// const findCmd = [videosDirectory].concat(options.flags || ['-maxdepth','1'])
|
||||
|
||||
// Write the `sh` script
|
||||
try{
|
||||
fs.writeFileSync(
|
||||
tempDirectory + 'orphanCheck.sh',
|
||||
`find "${s.checkCorrectPathEnding(videosDirectory,true)}" -maxdepth 1 -type f -exec stat -c "%n" {} + | sort -r | head -n ${options.checkMax}`
|
||||
);
|
||||
}catch(err){
|
||||
} catch(err) {
|
||||
console.log('Failed scanForOrphanedVideos', monitor.ke, monitor.mid)
|
||||
response.err = err.toString()
|
||||
resolve(response)
|
||||
return
|
||||
return resolve(response)
|
||||
}
|
||||
|
||||
let listing = spawn('sh',[tempDirectory + 'orphanCheck.sh'])
|
||||
// const onData = options.onData ? options.onData : () => {}
|
||||
const onError = options.onError ? options.onError : s.systemLog
|
||||
|
||||
const onExit = async () => {
|
||||
try{
|
||||
try {
|
||||
listing.kill('SIGTERM')
|
||||
await fs.promises.rm(tempDirectory + 'orphanCheck.sh')
|
||||
}catch(err){
|
||||
} catch(err) {
|
||||
s.debugLog(err)
|
||||
}
|
||||
delete(listing)
|
||||
}
|
||||
|
||||
const onFinish = () => {
|
||||
if(!finished){
|
||||
finished = true
|
||||
|
@ -103,9 +102,9 @@ module.exports = (s,config,lang) => {
|
|||
onExit()
|
||||
}
|
||||
}
|
||||
|
||||
const processLine = async (filePath) => {
|
||||
let filename = filePath.split('/')
|
||||
filename = `${filename[filename.length - 1]}`.trim()
|
||||
let filename = filePath.split('/').pop().trim()
|
||||
if(filename && filename.indexOf('-') > -1 && filename.indexOf('.') > -1){
|
||||
const { status } = await checkIfVideoIsOrphaned(monitor,videosDirectory,filename)
|
||||
if(status === 2){
|
||||
|
@ -117,21 +116,45 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------
|
||||
// Inactivity logic: if no data has arrived for 10 seconds, kill the process
|
||||
// ------------------------------------------------------------------------------
|
||||
let lastDataTimestamp = Date.now()
|
||||
const INACTIVITY_TIMEOUT = 10000
|
||||
|
||||
const checkInactivity = () => {
|
||||
if(finished) return // If we've already finished, do nothing
|
||||
const now = Date.now()
|
||||
if(now - lastDataTimestamp >= INACTIVITY_TIMEOUT){
|
||||
// It's been more than 10 seconds since the last data event
|
||||
onFinish()
|
||||
} else {
|
||||
// Check again in 1 second
|
||||
setTimeout(checkInactivity, 1000)
|
||||
}
|
||||
}
|
||||
// Start the inactivity checker
|
||||
setTimeout(checkInactivity, 1000)
|
||||
// ------------------------------------------------------------------------------
|
||||
|
||||
listing.stdout.on('data', async (d) => {
|
||||
// Reset the inactivity timer
|
||||
lastDataTimestamp = Date.now()
|
||||
|
||||
const filePathLines = d.toString().split('\n')
|
||||
var i;
|
||||
for (i = 0; i < filePathLines.length; i++) {
|
||||
for (let i = 0; i < filePathLines.length; i++) {
|
||||
await processLine(filePathLines[i])
|
||||
}
|
||||
})
|
||||
listing.stderr.on('data', d=>onError(d.toString()))
|
||||
listing.stderr.on('data', d => onError(d.toString()))
|
||||
listing.on('close', (code) => {
|
||||
// s.debugLog(`findOrphanedVideos ${monitor.ke} : ${monitor.mid} process exited with code ${code}`);
|
||||
setTimeout(() => {
|
||||
onFinish()
|
||||
},1000)
|
||||
});
|
||||
}else{
|
||||
} else {
|
||||
// If we are not going to check for orphans, just resolve
|
||||
resolve(response)
|
||||
}
|
||||
})
|
||||
|
@ -244,13 +267,16 @@ module.exports = (s,config,lang) => {
|
|||
startTime,
|
||||
endTime,
|
||||
searchQuery,
|
||||
monitorRestrictions
|
||||
monitorRestrictions,
|
||||
andOnly
|
||||
}){
|
||||
const theSearches = searchQuery.split(',').map(query => ['objects','LIKE',`%${query.trim()}%`]);
|
||||
const lastIndex = theSearches.length - 1;
|
||||
theSearches.forEach(function(item, n){
|
||||
if(n !== 0)theSearches[n] = ['or', ...item];
|
||||
});
|
||||
if(!andOnly){
|
||||
theSearches.forEach(function(item, n){
|
||||
if(n !== 0)theSearches[n] = ['or', ...item];
|
||||
});
|
||||
}
|
||||
const initialEventQuery = [
|
||||
['ke','=',groupKey],
|
||||
];
|
||||
|
@ -897,7 +923,7 @@ module.exports = (s,config,lang) => {
|
|||
await fsP.writeFile(framePath, frameBuffer)
|
||||
await s.createTimelapseFrameAndInsert(activeMonitor,location,frameFilename, frameTime._d)
|
||||
}catch(err){
|
||||
console.error(err)
|
||||
s.debugLog(err)
|
||||
}
|
||||
}
|
||||
// console.error('Completed Saving Frame from New Video!', framePath)
|
||||
|
|
|
@ -127,7 +127,11 @@ module.exports = function(s,config,lang){
|
|||
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)k.objects = [...new Set(listOTags)].filter(item => !!item).join(',');
|
||||
if(listOTags && !k.objects){
|
||||
k.objects = [...new Set(listOTags)].filter(item => !!item).join(',');
|
||||
}else if(k.objects[0] instanceof Object){
|
||||
k.objects = k.objects.map(matrix => matrix.tag).join(',').split(',').filter(item => !!item).join(',')
|
||||
}
|
||||
k.filename = k.filename || k.file
|
||||
k.ext = k.ext || e.ext || k.filename.split('.')[1]
|
||||
k.stat = fs.statSync(k.dir+k.file)
|
||||
|
@ -152,6 +156,7 @@ module.exports = function(s,config,lang){
|
|||
sendVideoToMasterNode(filePath,response)
|
||||
}else{
|
||||
var href = '/videos/'+e.ke+'/'+e.mid+'/'+k.filename
|
||||
|
||||
const monitorEventsCounted = activeMonitor.detector_motion_count
|
||||
s.txWithSubPermissions({
|
||||
f: 'video_build_success',
|
||||
|
|
|
@ -163,9 +163,20 @@ module.exports = function(s,config,lang,app,io){
|
|||
app.get(config.webPaths.apiPrefix+':auth/userInfo/:ke',function (req,res){
|
||||
var response = {ok:false};
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
s.auth(req.params, async function(user){
|
||||
response.ok = true
|
||||
response.user = user
|
||||
const { rows } = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: "ke,uid,mail,details",
|
||||
table: "Users",
|
||||
where: [
|
||||
['ke','=', req.params.ke],
|
||||
['uid','=', user.uid],
|
||||
]
|
||||
});
|
||||
const userInfo = rows[0];
|
||||
userInfo.details = JSON.parse(userInfo.details)
|
||||
response.user = userInfo;
|
||||
res.end(s.prettyPrint(response));
|
||||
},res,req);
|
||||
})
|
||||
|
@ -512,115 +523,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
res.end(s.prettyPrint({ok:true}))
|
||||
})
|
||||
})
|
||||
// /**
|
||||
// * Page : Montage - stand alone squished view with gridstackjs
|
||||
// */
|
||||
// app.get([
|
||||
// config.webPaths.apiPrefix+':auth/grid/:ke',
|
||||
// config.webPaths.apiPrefix+':auth/grid/:ke/:group',
|
||||
// config.webPaths.apiPrefix+':auth/cycle/:ke',
|
||||
// config.webPaths.apiPrefix+':auth/cycle/:ke/:group'
|
||||
// ], function(req,res) {
|
||||
// s.auth(req.params,function(user){
|
||||
// if(user.permissions.get_monitors==="0"){
|
||||
// res.end(user.lang['Not Permitted'])
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// req.params.protocol=req.protocol;
|
||||
// req.sql='SELECT * FROM Monitors WHERE mode!=? AND mode!=? AND ke=?';req.ar=['stop','idle',req.params.ke];
|
||||
// if(!req.params.id){
|
||||
// if(user.details.sub&&user.details.monitors&&user.details.allmonitors!=='1'){
|
||||
// try{user.details.monitors=JSON.parse(user.details.monitors);}catch(er){}
|
||||
// req.or=[];
|
||||
// user.details.monitors.forEach(function(v,n){
|
||||
// req.or.push('mid=?');req.ar.push(v)
|
||||
// })
|
||||
// req.sql+=' AND ('+req.or.join(' OR ')+')'
|
||||
// }
|
||||
// }else{
|
||||
// if(!user.details.sub||user.details.allmonitors!=='0'||user.details.monitors.indexOf(req.params.id)>-1){
|
||||
// req.sql+=' and mid=?';req.ar.push(req.params.id)
|
||||
// }else{
|
||||
// res.end(user.lang['There are no monitors that you can view with this account.']);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// s.sqlQuery(req.sql,req.ar,function(err,r){
|
||||
// if(req.params.group){
|
||||
// var filteredByGroupCheck = {};
|
||||
// var filteredByGroup = [];
|
||||
// r.forEach(function(v,n){
|
||||
// var details = JSON.parse(r[n].details);
|
||||
// try{
|
||||
// req.params.group.split('|').forEach(function(group){
|
||||
// var groups = JSON.parse(details.groups);
|
||||
// if(groups.indexOf(group) > -1 && !filteredByGroupCheck[v.mid]){
|
||||
// filteredByGroupCheck[v.mid] = true;
|
||||
// filteredByGroup.push(v)
|
||||
// }
|
||||
// })
|
||||
// }catch(err){
|
||||
//
|
||||
// }
|
||||
// })
|
||||
// r = filteredByGroup;
|
||||
// }
|
||||
// r.forEach(function(v,n){
|
||||
// if(s.group[v.ke]&&s.group[v.ke].activeMonitors[v.mid]&&s.group[v.ke].activeMonitors[v.mid].watch){
|
||||
// r[n].currentlyWatching=Object.keys(s.group[v.ke].activeMonitors[v.mid].watch).length
|
||||
// }
|
||||
// r[n].subStream={}
|
||||
// var details = JSON.parse(r[n].details)
|
||||
// if(details.snap==='1'){
|
||||
// r[n].subStream.jpeg = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg'
|
||||
// }
|
||||
// if(details.stream_channels&&details.stream_channels!==''){
|
||||
// try{
|
||||
// details.stream_channels=JSON.parse(details.stream_channels)
|
||||
// r[n].channels=[]
|
||||
// details.stream_channels.forEach(function(b,m){
|
||||
// var streamURL
|
||||
// switch(b.stream_type){
|
||||
// case'mjpeg':
|
||||
// streamURL='/'+req.params.auth+'/mjpeg/'+v.ke+'/'+v.mid+'/'+m
|
||||
// break;
|
||||
// case'hls':
|
||||
// streamURL='/'+req.params.auth+'/hls/'+v.ke+'/'+v.mid+'/'+m+'/s.m3u8'
|
||||
// break;
|
||||
// case'h264':
|
||||
// streamURL='/'+req.params.auth+'/h264/'+v.ke+'/'+v.mid+'/'+m
|
||||
// break;
|
||||
// case'flv':
|
||||
// streamURL='/'+req.params.auth+'/flv/'+v.ke+'/'+v.mid+'/'+m+'/s.flv'
|
||||
// break;
|
||||
// case'mp4':
|
||||
// streamURL='/'+req.params.auth+'/mp4/'+v.ke+'/'+v.mid+'/'+m+'/s.mp4'
|
||||
// break;
|
||||
// }
|
||||
// r[n].channels.push(streamURL)
|
||||
// })
|
||||
// }catch(err){
|
||||
// s.userLog(req.params,{type:'Broken Monitor Object',msg:'Stream Channels Field is damaged. Skipping.'})
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// var page = config.renderPaths.grid
|
||||
// if(req.path.indexOf('/cycle/') > -1){
|
||||
// page = config.renderPaths.cycle
|
||||
// }
|
||||
// s.renderPage(req,res,page,{
|
||||
// data:Object.assign(req.params,req.query),
|
||||
// baseUrl:req.protocol+'://'+req.hostname,
|
||||
// config: s.getConfigWithBranding(req.hostname),
|
||||
// lang:user.lang,
|
||||
// $user:user,
|
||||
// monitors:r,
|
||||
// query:req.query
|
||||
// });
|
||||
// })
|
||||
// },res,req)
|
||||
// });
|
||||
/**
|
||||
* API : Get TV Channels (Monitor Streams) json
|
||||
*/
|
||||
|
@ -1128,6 +1030,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
res.setHeader('Content-Type', 'application/json');
|
||||
s.auth(req.params,function(user){
|
||||
const searchQuery = s.getPostData(req,'search')
|
||||
const andOnly = s.getPostData(req,'andOnly') == '1'
|
||||
const startTime = s.getPostData(req,'start')
|
||||
const endTime = s.getPostData(req,'end')
|
||||
const monitorId = req.params.id
|
||||
|
@ -1158,6 +1061,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
endTime,
|
||||
searchQuery,
|
||||
monitorRestrictions,
|
||||
andOnly,
|
||||
}).then((response) => {
|
||||
if(response && response.rows){
|
||||
s.buildVideoLinks(response.rows,{
|
||||
|
@ -1528,8 +1432,8 @@ module.exports = function(s,config,lang,app,io){
|
|||
/**
|
||||
* API : Get Video File
|
||||
*/
|
||||
const videoRowCaches = {}
|
||||
const videoRowCacheTimeouts = {}
|
||||
const videoRowCaches = {}
|
||||
const videoRowCacheTimeouts = {}
|
||||
app.get(config.webPaths.apiPrefix+':auth/videos/:ke/:id/:file', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const groupKey = req.params.ke
|
||||
|
@ -2244,6 +2148,83 @@ module.exports = function(s,config,lang,app,io){
|
|||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Get Available Languages
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/languages/:ke',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok: true,
|
||||
list: s.listOfPossibleLanguages
|
||||
}
|
||||
s.closeJsonResponse(res,endData)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Get Storage Locations
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/storageLocations/:ke',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const endData = {
|
||||
ok: true,
|
||||
list: s.listOfStorage
|
||||
}
|
||||
s.closeJsonResponse(res,endData)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Get Hardware Acceleration choices
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/hardwareAccels/:ke',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const endData = {
|
||||
ok: true,
|
||||
list: s.listOfHwAccels
|
||||
}
|
||||
s.closeJsonResponse(res,endData)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Get Audio File choices
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/addStorage/:ke',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const endData = {
|
||||
ok: true,
|
||||
list: s.dir.addStorage
|
||||
}
|
||||
s.closeJsonResponse(res,endData)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Get Uploader choices
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/uploaderFields/:ke',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const fields = s.definitions["Account Settings"].blocks["Uploaders"];
|
||||
const endData = {
|
||||
ok: true,
|
||||
fields
|
||||
}
|
||||
s.closeJsonResponse(res,endData)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Get Admin API Prefix
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/getAdminApiPrefix/:ke',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const endData = {
|
||||
ok: true,
|
||||
adminApiPrefix: config.webPaths.adminApiPrefix
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.ok = false;
|
||||
endData.adminApiPrefix = null;
|
||||
}
|
||||
s.closeJsonResponse(res,endData)
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* Robots.txt
|
||||
*/
|
||||
app.get('/robots.txt', function (req,res){
|
||||
|
|
|
@ -10,6 +10,7 @@ var httpProxy = require('http-proxy');
|
|||
var proxy = httpProxy.createProxyServer({})
|
||||
var ejs = require('ejs');
|
||||
module.exports = function(s,config,lang,app){
|
||||
const { getAdminUser } = require('./user/utils.js')(s,config,lang);
|
||||
function cantLiveStreamPermission(user,monitorId,permission){
|
||||
const {
|
||||
monitorPermissions,
|
||||
|
@ -419,7 +420,7 @@ module.exports = function(s,config,lang,app){
|
|||
* Page : Get WallView
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/wallview/:ke', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
s.auth(req.params, async function(user){
|
||||
const authKey = req.params.auth
|
||||
const groupKey = req.params.ke
|
||||
if(
|
||||
|
@ -429,6 +430,7 @@ module.exports = function(s,config,lang,app){
|
|||
res.end(user.lang['Not Permitted'])
|
||||
return
|
||||
}
|
||||
const $user = await getAdminUser(groupKey, user.uid);
|
||||
s.renderPage(req,res,config.renderPaths.wallview,{
|
||||
forceUrlPrefix: req.query.host || '',
|
||||
data: req.params,
|
||||
|
@ -437,7 +439,7 @@ module.exports = function(s,config,lang,app){
|
|||
config: s.getConfigWithBranding(req.hostname),
|
||||
define: s.getDefinitonFile(user.details ? user.details.lang : config.lang),
|
||||
lang: lang,
|
||||
$user: user,
|
||||
$user,
|
||||
authKey: authKey,
|
||||
groupKey: groupKey,
|
||||
originalURL: s.getOriginalUrl(req)
|
||||
|
|
|
@ -26,8 +26,8 @@ module.exports = function(s,config,lang,app){
|
|||
startDate: req.query.start,
|
||||
endDate: req.query.end,
|
||||
startOperator: req.query.startOperator,
|
||||
endOperator: req.query.endOperator,
|
||||
limit: req.query.limit || 30,
|
||||
endIsStartTo: true,
|
||||
limit: req.query.limit,
|
||||
},(response) => {
|
||||
response.rows.forEach(function(v,n){
|
||||
response.rows[n].info = JSON.parse(v.info)
|
||||
|
@ -161,6 +161,7 @@ module.exports = function(s,config,lang,app){
|
|||
const configError = await modifyConfiguration(Object.assign(currentConfig,{
|
||||
subscriptionId: subscriptionId,
|
||||
}))
|
||||
config.subscriptionId = subscriptionId;
|
||||
if(configError)s.systemLog(configError)
|
||||
s.tx({f:'save_configuration'},'$')
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -42,10 +42,11 @@
|
|||
"mysql2": "^3.9.7",
|
||||
"node-abort-controller": "^3.0.1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-fstab": "^1.0.12",
|
||||
"node-shinobi": "^1.0.4",
|
||||
"node-ssh": "^12.0.4",
|
||||
"nodemailer": "^6.7.1",
|
||||
"onvif": "^0.7.1",
|
||||
"onvif": "^0.8.0",
|
||||
"pam-diff": "^1.1.0",
|
||||
"path": "^0.12.7",
|
||||
"pipe2pam": "^0.6.2",
|
||||
|
|
|
@ -17,11 +17,12 @@
|
|||
#wallview-controls{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
right: 30px;
|
||||
padding: 5px;
|
||||
opacity: 0;
|
||||
transition: 0.2s;
|
||||
z-index: 11;
|
||||
z-index: 12;
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
|
||||
#wallview-controls:hover{
|
||||
|
@ -42,7 +43,7 @@
|
|||
}
|
||||
|
||||
#wallview-monitorList .active i{
|
||||
display: inlin-block;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#wallview-canvas .wallview-video {
|
||||
|
@ -87,3 +88,22 @@
|
|||
#wallview-canvas .wallview-video:hover .wallview-item-controls{
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#wallview-monitorList {
|
||||
z-index: 11;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#wallview-monitorList-container {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
z-index: 11;
|
||||
width: 300px;
|
||||
height: 70vh;
|
||||
background: rgba(0,0,0,0.6);
|
||||
right: 0;
|
||||
bottom: 10vh;
|
||||
border-radius: 10px 0 0 10px;;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ $(document).ready(function(){
|
|||
var addStorageData = safeJsonParse($user.details.addStorage || '{}')
|
||||
var html = ''
|
||||
$.each(addStorage,function(n,storage){
|
||||
var theStorage = addStorageData[storage.path]
|
||||
var theStorage = addStorageData[storage.path] || {}
|
||||
html += `
|
||||
<div addStorageFields="${storage.path}">
|
||||
<div class="form-group">
|
||||
|
|
|
@ -2,7 +2,6 @@ $(document).ready(function(){
|
|||
onInitWebsocket(function(){
|
||||
loadMonitorsIntoMemory(function(data){
|
||||
setInterfaceCounts(data)
|
||||
openTab('initial')
|
||||
onDashboardReadyExecute()
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1089,6 +1089,9 @@ function onDashboardReadyExecute(theAction){
|
|||
function popImage(imageSrc){
|
||||
$('body').append(`<div class="popped-image"><img src="${imageSrc}"></div>`)
|
||||
}
|
||||
function popImageClose(imageSrc){
|
||||
$('.popped-image').remove()
|
||||
}
|
||||
function setSubmitButton(editorForm,text,icon,toggle){
|
||||
var submitButtons = editorForm.find('[type="submit"]').prop('disabled',toggle)
|
||||
submitButtons.html(`<i class="fa fa-${icon}"></i> ${text}`)
|
||||
|
|
|
@ -189,45 +189,43 @@ function initiateLiveGridPlayer(monitor){
|
|||
})
|
||||
break;
|
||||
case'mp4':
|
||||
setTimeout(function(){
|
||||
var stream = containerElement.find('.stream-element');
|
||||
var onPoseidonError = function(){
|
||||
// setTimeout(function(){
|
||||
// mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId})
|
||||
// },2000)
|
||||
var stream = containerElement.find('.stream-element');
|
||||
var onPoseidonError = function(){
|
||||
// setTimeout(function(){
|
||||
// mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId})
|
||||
// },2000)
|
||||
}
|
||||
if(!loadedPlayer.PoseidonErrorCount)loadedPlayer.PoseidonErrorCount = 0
|
||||
if(loadedPlayer.PoseidonErrorCount >= 5)return
|
||||
if(subStreamChannel ? details.substream.output.stream_flv_type === 'ws' : monitor.details.stream_flv_type === 'ws'){
|
||||
if(loadedPlayer.Poseidon){
|
||||
loadedPlayer.Poseidon.stop()
|
||||
revokeVideoPlayerUrl(monitorId)
|
||||
}
|
||||
if(!loadedPlayer.PoseidonErrorCount)loadedPlayer.PoseidonErrorCount = 0
|
||||
if(loadedPlayer.PoseidonErrorCount >= 5)return
|
||||
if(subStreamChannel ? details.substream.output.stream_flv_type === 'ws' : monitor.details.stream_flv_type === 'ws'){
|
||||
if(loadedPlayer.Poseidon){
|
||||
loadedPlayer.Poseidon.stop()
|
||||
revokeVideoPlayerUrl(monitorId)
|
||||
}
|
||||
try{
|
||||
loadedPlayer.Poseidon = new Poseidon({
|
||||
video: stream[0],
|
||||
auth_token: $user.auth_token,
|
||||
ke: monitor.ke,
|
||||
uid: $user.uid,
|
||||
id: monitor.mid,
|
||||
url: location.origin,
|
||||
path: websocketPath,
|
||||
query: websocketQuery,
|
||||
onError : onPoseidonError,
|
||||
channel : subStreamChannel
|
||||
})
|
||||
loadedPlayer.Poseidon.start();
|
||||
}catch(err){
|
||||
// onPoseidonError()
|
||||
console.log('onTryPoseidonError',err)
|
||||
}
|
||||
}else{
|
||||
stream.attr('src',getApiPrefix(`mp4`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.mp4?time=' + (new Date()).getTime())
|
||||
stream[0].onerror = function(err){
|
||||
console.error(err)
|
||||
}
|
||||
try{
|
||||
loadedPlayer.Poseidon = new Poseidon({
|
||||
video: stream[0],
|
||||
auth_token: $user.auth_token,
|
||||
ke: monitor.ke,
|
||||
uid: $user.uid,
|
||||
id: monitor.mid,
|
||||
url: location.origin,
|
||||
path: websocketPath,
|
||||
query: websocketQuery,
|
||||
onError : onPoseidonError,
|
||||
channel : subStreamChannel
|
||||
})
|
||||
loadedPlayer.Poseidon.start();
|
||||
}catch(err){
|
||||
// onPoseidonError()
|
||||
console.log('onTryPoseidonError',err)
|
||||
}
|
||||
},1000)
|
||||
}else{
|
||||
stream.attr('src',getApiPrefix(`mp4`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.mp4?time=' + (new Date()).getTime())
|
||||
stream[0].onerror = function(err){
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
break;
|
||||
case'flv':
|
||||
if (flvjs.isSupported()) {
|
||||
|
@ -345,6 +343,7 @@ function closeLiveGridPlayer(monitorId,killElement){
|
|||
var livePlayerElement = loadedLiveGrids[monitorId]
|
||||
if(livePlayerElement){
|
||||
if(livePlayerElement.hls){livePlayerElement.hls.destroy()}
|
||||
clearTimeout(livePlayerElement.m3uCheck)
|
||||
if(livePlayerElement.Poseidon){livePlayerElement.Poseidon.stop()}
|
||||
if(livePlayerElement.Base64){livePlayerElement.Base64.disconnect()}
|
||||
if(livePlayerElement.dash){livePlayerElement.dash.reset()}
|
||||
|
@ -452,7 +451,7 @@ function signalCheckLiveStream(options){
|
|||
}
|
||||
})
|
||||
}
|
||||
mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId});
|
||||
requestMonitorInit()
|
||||
}
|
||||
function succeededStreamCheck(){
|
||||
if(monitorConfig.signal_check_log == 1){
|
||||
|
@ -513,7 +512,7 @@ function signalCheckLiveStream(options){
|
|||
var errorStack = err.stack;
|
||||
function phraseFoundInErrorStack(x){return errorStack.indexOf(x) > -1}
|
||||
if(phraseFoundInErrorStack("The HTMLImageElement provided is in the 'broken' state.")){
|
||||
mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId});
|
||||
requestMonitorInit()
|
||||
}
|
||||
clearInterval(liveGridData.signal);
|
||||
delete(liveGridData.signal);
|
||||
|
@ -563,12 +562,7 @@ $(document).ready(function(e){
|
|||
fullScreenLiveGridStream(monitorItem)
|
||||
})
|
||||
.on('click','.reconnect-live-grid-monitor',function(){
|
||||
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
||||
mainSocket.f({
|
||||
f: 'monitor',
|
||||
ff: 'watch_on',
|
||||
id: monitorId
|
||||
})
|
||||
requestMonitorInit()
|
||||
})
|
||||
.on('click','.toggle-live-grid-monitor-fullscreen',function(){
|
||||
var monitorItem = $(this).parents('[data-mid]')
|
||||
|
@ -580,11 +574,12 @@ $(document).ready(function(e){
|
|||
// loadPreviouslyOpenedLiveGridBlocks()
|
||||
break;
|
||||
case'monitor_watch_off':case'monitor_stopping':
|
||||
var monitorId = d.mid || d.id
|
||||
closeLiveGridPlayer(monitorId,(d.f === 'monitor_watch_off'))
|
||||
if(monitorId === d.id){
|
||||
closeLiveGridPlayer(monitorId,(d.f === 'monitor_watch_off'))
|
||||
}
|
||||
break;
|
||||
case'monitor_status':
|
||||
if(monitorId === d.mid && d.code === 2 || d.code === 3){
|
||||
if(monitorId === d.id && (d.code === 2 || d.code === 3)){
|
||||
setTimeout(function(){
|
||||
requestMonitorInit()
|
||||
},2000)
|
||||
|
|
|
@ -81,3 +81,15 @@ function getQueryString(){
|
|||
})
|
||||
return theObject
|
||||
}
|
||||
function getListOfTagsFromMonitors(){
|
||||
var listOftags = {}
|
||||
$.each(loadedMonitors,function(monitorId,monitor){
|
||||
if(monitor.tags){
|
||||
monitor.tags.split(',').forEach((tag) => {
|
||||
if(!listOftags[tag])listOftags[tag] = [];
|
||||
listOftags[tag].push(monitorId)
|
||||
})
|
||||
}
|
||||
})
|
||||
return listOftags
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@ async function addExtender(extenderContainer){
|
|||
dashboardExtensions[extenderContainer].push(...extender)
|
||||
};
|
||||
}
|
||||
async function executeExtender(extenderContainer, args){
|
||||
async function addActionToExtender(extenderContainer, action){
|
||||
return on[extenderContainer](action)
|
||||
}
|
||||
async function executeExtender(extenderContainer, args = []){
|
||||
for(extender of dashboardExtensions[extenderContainer]){
|
||||
await extender(...args)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
function setGamepadMonitorSelection(monitorId) => {
|
||||
dashboardOptions('gamepadMonitorSelection', monitorId);
|
||||
selectedMonitor = `${monitorId}`;
|
||||
}
|
||||
|
||||
function canGame() {
|
||||
return "getGamepads" in navigator;
|
||||
}
|
||||
|
||||
function convertStickAxisTo2048(value){
|
||||
var newVal = parseInt((stickMax - stickBase) * value + stickBase)
|
||||
return newVal
|
||||
}
|
||||
|
||||
function getAnalogStickValues(gp, i, callback){
|
||||
var label = i === 0 ? 'left' : 'right'
|
||||
var horizontal = gp.axes[i] * outerDeadZone
|
||||
var vertical = gp.axes[i + 1] * outerDeadZone
|
||||
var newH = convertStickAxisTo2048(horizontal > deadZoneThreshold || horizontal < -deadZoneThreshold ? horizontal : 0)
|
||||
var newV = convertStickAxisTo2048((vertical > deadZoneThreshold || vertical < -deadZoneThreshold ? vertical : 0) * -1)
|
||||
if(
|
||||
newH !== lastState.sticks[label].h ||
|
||||
newV !== lastState.sticks[label].v
|
||||
){
|
||||
callback(label, newH, newV)
|
||||
}
|
||||
lastState.sticks[label].h = newH
|
||||
lastState.sticks[label].v = newV
|
||||
}
|
||||
|
||||
function getStickValue(gp, i, callback){
|
||||
var label = `axis${axis}`;
|
||||
var axis = gp.axes[i] * outerDeadZone
|
||||
var newH = convertStickAxisTo2048(axis > deadZoneThreshold || axis < -deadZoneThreshold ? axis : 0)
|
||||
if(newH !== lastState[label]){
|
||||
callback(newH)
|
||||
}
|
||||
lastState[label] = newH
|
||||
}
|
||||
|
||||
function getButtonsPressed(gp, callback, offCallback = () => {}){
|
||||
$.each(keyLegend,function(code,key){
|
||||
if(gp.buttons[code] && gp.buttons[code].pressed){
|
||||
if(!lastState[key]){
|
||||
buttonsPressed[code] = true;
|
||||
callback(code)
|
||||
}
|
||||
lastState[key] = true
|
||||
}else{
|
||||
if(lastState[key]){
|
||||
buttonsPressed[code] = false;
|
||||
offCallback(code)
|
||||
}
|
||||
lastState[key] = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function sendPtzCommand(direction, doMove){
|
||||
runPtzMove(selectedMonitor, direction, doMove)
|
||||
}
|
||||
|
||||
function sentPtzToHome(){
|
||||
runPtzCommand(selectedMonitor, 'center')
|
||||
}
|
|
@ -0,0 +1,390 @@
|
|||
$(document).ready(function() {
|
||||
var selectedController = 0;
|
||||
var keyLegend = {
|
||||
"0": "b",
|
||||
"1": "a",
|
||||
"2": "y",
|
||||
"3": "x",
|
||||
"4": "l",
|
||||
"5": "r",
|
||||
"6": "zl",
|
||||
"7": "zr",
|
||||
"8": "minus",
|
||||
"9": "plus",
|
||||
"10": "l_stick",
|
||||
"11": "r_stick",
|
||||
"12": "up",
|
||||
"13": "down",
|
||||
"14": "left",
|
||||
"15": "right",
|
||||
}
|
||||
var lastState = {
|
||||
sticks: {
|
||||
left: {},
|
||||
right: {},
|
||||
}
|
||||
}
|
||||
var lastPtzDirection = {}
|
||||
var buttonsPressed = {}
|
||||
var hasGP = false;
|
||||
var repGP;
|
||||
var reportInterval = 200
|
||||
var stickBase = 2048
|
||||
var stickMax = 4096
|
||||
var deadZoneThreshold = 0.35
|
||||
var outerDeadZone = 1.01
|
||||
var selectedMonitor = dashboardOptions().gamepadMonitorSelection;
|
||||
var monitorKeys = {};
|
||||
var onMonitorOpenForGamepad = () => {}
|
||||
var sequenceButtonPressList = []
|
||||
var sequenceButtonPressTimeout = null
|
||||
var buttonPressAction = null;
|
||||
window.setGamepadMonitorSelection = (monitorId) => {
|
||||
dashboardOptions('gamepadMonitorSelection', monitorId);
|
||||
selectedMonitor = `${monitorId}`;
|
||||
}
|
||||
|
||||
function canGame() {
|
||||
return "getGamepads" in navigator;
|
||||
}
|
||||
|
||||
function convertStickAxisTo2048(value){
|
||||
var newVal = parseInt((stickMax - stickBase) * value + stickBase)
|
||||
return newVal
|
||||
}
|
||||
|
||||
function getAnalogStickValues(gp, i, callback){
|
||||
var label = i === 0 ? 'left' : 'right'
|
||||
var horizontal = gp.axes[i] * outerDeadZone
|
||||
var vertical = gp.axes[i + 1] * outerDeadZone
|
||||
var newH = convertStickAxisTo2048(horizontal > deadZoneThreshold || horizontal < -deadZoneThreshold ? horizontal : 0)
|
||||
var newV = convertStickAxisTo2048((vertical > deadZoneThreshold || vertical < -deadZoneThreshold ? vertical : 0) * -1)
|
||||
if(
|
||||
newH !== lastState.sticks[label].h ||
|
||||
newV !== lastState.sticks[label].v
|
||||
){
|
||||
callback(label, newH, newV)
|
||||
}
|
||||
lastState.sticks[label].h = newH
|
||||
lastState.sticks[label].v = newV
|
||||
}
|
||||
|
||||
function getStickValue(gp, i, callback){
|
||||
var label = `axis${axis}`;
|
||||
var axis = gp.axes[i] * outerDeadZone
|
||||
var newH = convertStickAxisTo2048(axis > deadZoneThreshold || axis < -deadZoneThreshold ? axis : 0)
|
||||
if(newH !== lastState[label]){
|
||||
callback(newH)
|
||||
}
|
||||
lastState[label] = newH
|
||||
}
|
||||
|
||||
function getButtonsPressed(gp, callback, offCallback = () => {}){
|
||||
$.each(keyLegend,function(code,key){
|
||||
if(gp.buttons[code] && gp.buttons[code].pressed){
|
||||
if(!lastState[key]){
|
||||
buttonsPressed[code] = true;
|
||||
callback(code)
|
||||
}
|
||||
lastState[key] = true
|
||||
}else{
|
||||
if(lastState[key]){
|
||||
buttonsPressed[code] = false;
|
||||
offCallback(code)
|
||||
}
|
||||
lastState[key] = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function setCameraFromButtonCode(buttonCode = 0, preAdded){
|
||||
const addedOneToButtonCode = preAdded ? buttonCode : parseInt(buttonCode) + 1
|
||||
try{
|
||||
const monitor = loadedMonitors[monitorKeys[addedOneToButtonCode]];
|
||||
const isFullscreened = !!document.fullscreenElement;
|
||||
if(isFullscreened) {
|
||||
document.exitFullscreen()
|
||||
closeAllLiveGridPlayers(true)
|
||||
}
|
||||
openMonitorInLiveGrid(monitor.mid, function(){
|
||||
if(isFullscreened) {
|
||||
fullScreenLiveGridStreamById(monitor.mid)
|
||||
}
|
||||
})
|
||||
|
||||
}catch(err){
|
||||
new PNotify({
|
||||
title: lang['Invalid Action'],
|
||||
text: `${lang.ptzControlIdNotFound}<br><br>${lang['Button Code']} : ${addedOneToButtonCode}`,
|
||||
type: 'warning'
|
||||
});
|
||||
console.log('No Monitor Associated :', buttonCode)
|
||||
}
|
||||
}
|
||||
|
||||
// function setCameraFromButtonNumbers(){
|
||||
// const buttons = Object.keys(buttonsPressed).filter(code => buttonsPressed[code]);
|
||||
// console.log('pressed', buttons)
|
||||
// if(buttons.length > 1){
|
||||
//
|
||||
// }else if(buttons.length > 0){
|
||||
// const monitor = loadedMonitors[monitorKeys[buttons[0]]];
|
||||
// console.log(monitorKeys[buttons[0]])
|
||||
// openMonitorInLiveGrid(monitor.mid)
|
||||
// }
|
||||
// }
|
||||
|
||||
function openMonitorInLiveGrid(monitorId, callback){
|
||||
lastPtzDirection = {};
|
||||
setGamepadMonitorSelection(monitorId)
|
||||
mainSocket.f({
|
||||
f: 'monitor',
|
||||
ff: 'watch_on',
|
||||
id: monitorId
|
||||
})
|
||||
onMonitorOpenForGamepad = (monitorId) => {
|
||||
setTimeout(() => {
|
||||
if(monitorId === selectedMonitor){
|
||||
onMonitorOpenForGamepad = () => {}
|
||||
if(callback)callback()
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
}
|
||||
|
||||
function sendPtzCommand(direction, doMove){
|
||||
runPtzMove(selectedMonitor, direction, doMove)
|
||||
}
|
||||
|
||||
function sentPtzToHome(){
|
||||
runPtzCommand(selectedMonitor, 'center')
|
||||
}
|
||||
|
||||
function translatePointTiltStick(x, y){
|
||||
if(x > stickBase && !lastPtzDirection['right']){
|
||||
lastPtzDirection['right'] = true
|
||||
lastPtzDirection['left'] = false
|
||||
// sendPtzCommand('left', false)
|
||||
sendPtzCommand('right', true)
|
||||
}else if(x < stickBase && !lastPtzDirection['left']){
|
||||
lastPtzDirection['left'] = true
|
||||
lastPtzDirection['right'] = false
|
||||
// sendPtzCommand('right', false)
|
||||
sendPtzCommand('left', true)
|
||||
}else if(x === stickBase){
|
||||
if(lastPtzDirection['right'])sendPtzCommand('right', false)
|
||||
if(lastPtzDirection['left'])sendPtzCommand('left', false)
|
||||
lastPtzDirection['right'] = false
|
||||
lastPtzDirection['left'] = false
|
||||
}
|
||||
if(y > stickBase && !lastPtzDirection['up']){
|
||||
lastPtzDirection['up'] = true
|
||||
lastPtzDirection['down'] = false
|
||||
// sendPtzCommand('down', false)
|
||||
sendPtzCommand('up', true)
|
||||
}else if(y < stickBase && !lastPtzDirection['down']){
|
||||
lastPtzDirection['down'] = true
|
||||
lastPtzDirection['up'] = false
|
||||
// sendPtzCommand('up', false)
|
||||
sendPtzCommand('down', true)
|
||||
}else if(y === stickBase){
|
||||
if(lastPtzDirection['up'])sendPtzCommand('up', false)
|
||||
if(lastPtzDirection['down'])sendPtzCommand('down', false)
|
||||
lastPtzDirection['down'] = false
|
||||
lastPtzDirection['up'] = false
|
||||
}
|
||||
console.log(lastPtzDirection)
|
||||
}
|
||||
|
||||
function translateZoomAxis(value){
|
||||
if(value > stickBase && !lastPtzDirection['zoom_in']){
|
||||
lastPtzDirection['zoom_in'] = true
|
||||
lastPtzDirection['zoom_out'] = false
|
||||
// sendPtzCommand('zoom_out', false)
|
||||
sendPtzCommand('zoom_in', true)
|
||||
}else if(value < stickBase && !lastPtzDirection['zoom_out']){
|
||||
lastPtzDirection['zoom_out'] = true
|
||||
lastPtzDirection['zoom_in'] = false
|
||||
// sendPtzCommand('zoom_in', false)
|
||||
sendPtzCommand('zoom_out', true)
|
||||
}else if(value === stickBase){
|
||||
if(lastPtzDirection['zoom_in'])sendPtzCommand('zoom_in', false)
|
||||
if(lastPtzDirection['zoom_out'])sendPtzCommand('zoom_out', false)
|
||||
lastPtzDirection['zoom_in'] = false
|
||||
lastPtzDirection['zoom_out'] = false
|
||||
}
|
||||
}
|
||||
|
||||
function reportOnXboxGamepad() {
|
||||
try{
|
||||
var gp = navigator.getGamepads()[0];
|
||||
getButtonsPressed(gp, function(buttonCode){
|
||||
if(buttonCode == 6){
|
||||
sendPtzCommand('zoom_out', true)
|
||||
}else if(buttonCode == 7){
|
||||
sendPtzCommand('zoom_in', true)
|
||||
}else if(buttonCode == 8){
|
||||
if($('.popped-image').length > 0){
|
||||
closeSnapshot()
|
||||
}else{
|
||||
openSnapshot()
|
||||
}
|
||||
}else if(buttonCode == 9){
|
||||
sentPtzToHome()
|
||||
}else if(buttonCode == 11){
|
||||
if (!document.fullscreenElement) {
|
||||
fullScreenLiveGridStreamById(selectedMonitor)
|
||||
}else{
|
||||
document.exitFullscreen()
|
||||
}
|
||||
}else{
|
||||
buttonPressAction(buttonCode)
|
||||
}
|
||||
}, function(buttonCode){
|
||||
if(buttonCode == 6){
|
||||
sendPtzCommand('zoom_out', false)
|
||||
}else if(buttonCode == 7){
|
||||
sendPtzCommand('zoom_in', false)
|
||||
}
|
||||
})
|
||||
getAnalogStickValues(gp, 0, function(stick, x, y){
|
||||
translatePointTiltStick(x, y)
|
||||
})
|
||||
getAnalogStickValues(gp, 2, function(stick, x, y){
|
||||
translateZoomAxis(y)
|
||||
})
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
// stopReporting()
|
||||
}
|
||||
}
|
||||
|
||||
function reportOnGenericGamepad() {
|
||||
try{
|
||||
const gp = navigator.getGamepads()[0];
|
||||
getButtonsPressed(gp, function(buttonCode){
|
||||
if(buttonCode == 10){
|
||||
closeSnapshot()
|
||||
openSnapshot()
|
||||
}else if(buttonCode == 11){
|
||||
closeSnapshot()
|
||||
}else{
|
||||
buttonPressAction(buttonCode)
|
||||
}
|
||||
},function(buttonCode){
|
||||
|
||||
})
|
||||
getAnalogStickValues(gp, 0, function(stick, x, y){
|
||||
translatePointTiltStick(x, y)
|
||||
})
|
||||
getStickValue(gp, 2,function(value){
|
||||
translateZoomAxis(value)
|
||||
})
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
// stopReporting()
|
||||
}
|
||||
}
|
||||
|
||||
function openSnapshot(){
|
||||
getSnapshot(loadedMonitors[selectedMonitor],function(url){
|
||||
popImage(url)
|
||||
})
|
||||
}
|
||||
function closeSnapshot(){
|
||||
popImageClose()
|
||||
}
|
||||
|
||||
|
||||
function startReporting(){
|
||||
if(hasGP){
|
||||
console.log('Reading Gamepad')
|
||||
repGP = window.setInterval(reportOnGamepad, reportInterval);
|
||||
}
|
||||
}
|
||||
|
||||
function stopReporting(){
|
||||
console.log('Stopping Gamepad')
|
||||
window.clearInterval(repGP)
|
||||
}
|
||||
|
||||
function generateMonitorKeysFromPtzIds(){
|
||||
monitorKeys = []
|
||||
Object.values(loadedMonitors)
|
||||
.filter(item => !!parseInt(item.details.ptz_id))
|
||||
.sort((a, b) => parseInt(b.details.ptz_id) - parseInt(a.details.ptz_id))
|
||||
.forEach((item) => {
|
||||
console.log(item.details.ptz_id)
|
||||
monitorKeys[item.details.ptz_id] = item.mid;
|
||||
});
|
||||
console.log(monitorKeys)
|
||||
}
|
||||
|
||||
function setControllerType(gamepadId){
|
||||
switch(true){
|
||||
case gamepadId.includes('Xbox'):
|
||||
reportInterval = 200;
|
||||
reportOnGamepad = reportOnXboxGamepad
|
||||
buttonPressAction = setCameraFromButtonCode
|
||||
console.log('Xbox Controller found!')
|
||||
break;
|
||||
default:
|
||||
reportInterval = 50;
|
||||
reportOnGamepad = reportOnGenericGamepad
|
||||
buttonPressAction = sequenceButtonPress
|
||||
break;
|
||||
}
|
||||
}
|
||||
var reportOnGamepad = reportOnXboxGamepad;
|
||||
|
||||
function sequenceButtonPress(buttonCode){
|
||||
sequenceButtonPressList.push(buttonCode)
|
||||
clearTimeout(sequenceButtonPressTimeout)
|
||||
sequenceButtonPressTimeout = setTimeout(() => {
|
||||
const newButtonCode = parseInt(sequenceButtonPressList.map(item => `${parseInt(item) + 1}`).join(''))
|
||||
setCameraFromButtonCode(newButtonCode, true)
|
||||
sequenceButtonPressList = []
|
||||
},300)
|
||||
}
|
||||
|
||||
if(canGame()) {
|
||||
$(window).on("gamepadconnected", function(e) {
|
||||
hasGP = true;
|
||||
if(tabTree.name === 'liveGrid'){
|
||||
startReporting()
|
||||
}
|
||||
const gamepadName = e.originalEvent.gamepad.id;
|
||||
setControllerType(gamepadName)
|
||||
console.log('Gamepad Connected!', gamepadName)
|
||||
})
|
||||
.on("gamepaddisconnected", function() {
|
||||
if(!navigator.getGamepads()[0]){
|
||||
hasGP = false;
|
||||
console.log('Gamepad Disconnected!')
|
||||
}
|
||||
})
|
||||
}
|
||||
onDashboardReady(function(d){
|
||||
generateMonitorKeysFromPtzIds();
|
||||
})
|
||||
onWebSocketEvent(function(d){
|
||||
switch(d.f){
|
||||
case'monitor_edit':
|
||||
generateMonitorKeysFromPtzIds();
|
||||
break;
|
||||
case'monitor_watch_on':
|
||||
var monitorId = d.mid || d.id;
|
||||
onMonitorOpenForGamepad(monitorId)
|
||||
break;
|
||||
}
|
||||
})
|
||||
addOnTabOpen('liveGrid', function () {
|
||||
startReporting()
|
||||
})
|
||||
addOnTabReopen('liveGrid', function () {
|
||||
startReporting()
|
||||
})
|
||||
addOnTabAway('liveGrid', function () {
|
||||
stopReporting()
|
||||
})
|
||||
});
|
|
@ -35,6 +35,9 @@ function getLiveGridData(){
|
|||
}
|
||||
function getMonitorsPerRow(){
|
||||
var x
|
||||
if(dashboardOptions().montage_use != '1'){
|
||||
return '4'
|
||||
}
|
||||
switch(dashboardOptions().montage){
|
||||
case'1':
|
||||
x = '12'
|
||||
|
@ -422,45 +425,43 @@ function initiateLiveGridPlayer(monitor,subStreamChannel){
|
|||
})
|
||||
break;
|
||||
case'mp4':
|
||||
setTimeout(function(){
|
||||
var stream = containerElement.find('.stream-element');
|
||||
var onPoseidonError = function(){
|
||||
// setTimeout(function(){
|
||||
// mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId})
|
||||
// },2000)
|
||||
var stream = containerElement.find('.stream-element');
|
||||
var onPoseidonError = function(){
|
||||
// setTimeout(function(){
|
||||
// mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId})
|
||||
// },2000)
|
||||
}
|
||||
if(!loadedPlayer.PoseidonErrorCount)loadedPlayer.PoseidonErrorCount = 0
|
||||
if(loadedPlayer.PoseidonErrorCount >= 5)return
|
||||
if(subStreamChannel ? details.substream.output.stream_flv_type === 'ws' : monitor.details.stream_flv_type === 'ws'){
|
||||
if(loadedPlayer.Poseidon){
|
||||
loadedPlayer.Poseidon.stop()
|
||||
revokeVideoPlayerUrl(monitorId)
|
||||
}
|
||||
if(!loadedPlayer.PoseidonErrorCount)loadedPlayer.PoseidonErrorCount = 0
|
||||
if(loadedPlayer.PoseidonErrorCount >= 5)return
|
||||
if(subStreamChannel ? details.substream.output.stream_flv_type === 'ws' : monitor.details.stream_flv_type === 'ws'){
|
||||
if(loadedPlayer.Poseidon){
|
||||
loadedPlayer.Poseidon.stop()
|
||||
revokeVideoPlayerUrl(monitorId)
|
||||
}
|
||||
try{
|
||||
loadedPlayer.Poseidon = new Poseidon({
|
||||
video: stream[0],
|
||||
auth_token: $user.auth_token,
|
||||
ke: monitor.ke,
|
||||
uid: $user.uid,
|
||||
id: monitor.mid,
|
||||
url: location.origin,
|
||||
path: websocketPath,
|
||||
query: websocketQuery,
|
||||
onError : onPoseidonError,
|
||||
channel : subStreamChannel
|
||||
})
|
||||
loadedPlayer.Poseidon.start();
|
||||
}catch(err){
|
||||
// onPoseidonError()
|
||||
console.log('onTryPoseidonError',err)
|
||||
}
|
||||
}else{
|
||||
stream.attr('src',getApiPrefix(`mp4`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.mp4?time=' + (new Date()).getTime())
|
||||
stream[0].onerror = function(err){
|
||||
console.error(err)
|
||||
}
|
||||
try{
|
||||
loadedPlayer.Poseidon = new Poseidon({
|
||||
video: stream[0],
|
||||
auth_token: $user.auth_token,
|
||||
ke: monitor.ke,
|
||||
uid: $user.uid,
|
||||
id: monitor.mid,
|
||||
url: location.origin,
|
||||
path: websocketPath,
|
||||
query: websocketQuery,
|
||||
onError : onPoseidonError,
|
||||
channel : subStreamChannel
|
||||
})
|
||||
loadedPlayer.Poseidon.start();
|
||||
}catch(err){
|
||||
// onPoseidonError()
|
||||
console.log('onTryPoseidonError',err)
|
||||
}
|
||||
},1000)
|
||||
}else{
|
||||
stream.attr('src',getApiPrefix(`mp4`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.mp4?time=' + (new Date()).getTime())
|
||||
stream[0].onerror = function(err){
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
break;
|
||||
case'flv':
|
||||
if (flvjs.isSupported()) {
|
||||
|
@ -630,6 +631,7 @@ function closeLiveGridPlayer(monitorId,killElement){
|
|||
var loadedPlayer = loadedLiveGrids[monitorId]
|
||||
if(loadedPlayer){
|
||||
if(loadedPlayer.hls){loadedPlayer.hls.destroy()}
|
||||
clearTimeout(loadedPlayer.m3uCheck)
|
||||
if(loadedPlayer.Poseidon){loadedPlayer.Poseidon.stop()}
|
||||
if(loadedPlayer.Base64){loadedPlayer.Base64.disconnect()}
|
||||
if(loadedPlayer.dash){loadedPlayer.dash.reset()}
|
||||
|
@ -773,6 +775,10 @@ function fullScreenLiveGridStream(monitorItem){
|
|||
}
|
||||
fullScreenInit(videoElement[0])
|
||||
}
|
||||
function fullScreenLiveGridStreamById(monitorId){
|
||||
const monitorItem = liveGrid.find(`[data-mid="${monitorId}"]`)
|
||||
fullScreenLiveGridStream(monitorItem)
|
||||
}
|
||||
function toggleJpegMode(){
|
||||
var sendData = {
|
||||
f: 'monitor',
|
||||
|
@ -977,7 +983,7 @@ function setPauseScrollTimeout(){
|
|||
if(tabTree.name === 'liveGrid'){
|
||||
liveGridPauseScrollTimeout = setTimeout(function(){
|
||||
setPauseStatusForMonitorItems()
|
||||
},700)
|
||||
},200)
|
||||
}
|
||||
}
|
||||
function openAllLiveGridPlayers(){
|
||||
|
@ -1101,6 +1107,7 @@ $(document).ready(function(e){
|
|||
.on('click','.toggle-live-grid-monitor-ptz-controls',function(){
|
||||
var monitorItem = $(this).parents('[data-mid]').attr('data-mid')
|
||||
drawPtzControlsOnLiveGridBlock(monitorItem)
|
||||
setGamepadMonitorSelection()
|
||||
})
|
||||
.on('click','.toggle-live-grid-monitor-menu,.mdl-overlay-menu-backdrop',function(){
|
||||
var monitorItem = $(this).parents('[data-mid]')
|
||||
|
@ -1114,6 +1121,7 @@ $(document).ready(function(e){
|
|||
.on('click','.toggle-live-grid-monitor-fullscreen',function(){
|
||||
var monitorItem = $(this).parents('[data-mid]')
|
||||
fullScreenLiveGridStream(monitorItem)
|
||||
setGamepadMonitorSelection()
|
||||
})
|
||||
.on('click','.run-live-grid-monitor-pop',function(){
|
||||
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
||||
|
@ -1277,6 +1285,7 @@ $(document).ready(function(e){
|
|||
break;
|
||||
case'substream_start':
|
||||
loadedMonitors[d.mid].subStreamChannel = d.channel
|
||||
loadedMonitors[d.mid].subStreamActive = true
|
||||
showHideSubstreamActiveIcon(d.mid,true)
|
||||
setTimeout(() => {
|
||||
resetMonitorCanvas(d.mid,true,d.channel)
|
||||
|
@ -1284,6 +1293,7 @@ $(document).ready(function(e){
|
|||
break;
|
||||
case'substream_end':
|
||||
loadedMonitors[d.mid].subStreamChannel = null
|
||||
loadedMonitors[d.mid].subStreamActive = false
|
||||
resetMonitorCanvas(d.mid,true,null)
|
||||
showHideSubstreamActiveIcon(d.mid,false)
|
||||
break;
|
||||
|
|
|
@ -424,7 +424,9 @@ var copyMonitorSettingsToSelected = function(monitorConfig){
|
|||
})
|
||||
$.post(getApiPrefix()+'/configureMonitor/'+$user.ke+'/'+monitor.mid,{data:JSON.stringify(monitor)},function(d){
|
||||
debugLog(d)
|
||||
})
|
||||
}).fail(function(xhr, status, error) {
|
||||
console.error(error)
|
||||
});
|
||||
chosenMonitors[monitor.mid] = monitor;
|
||||
})
|
||||
}
|
||||
|
@ -570,7 +572,7 @@ function drawInputMapSelectorHtml(options,parent){
|
|||
function getPluginsList(monitorConfig){
|
||||
return new Promise((resolve) => {
|
||||
const chosenDetectors = safeJsonParse(monitorConfig.details).detectors_selected || [];
|
||||
$.get(getApiPrefix() + '/plugins/list',function(data){
|
||||
$.get(getApiPrefix('plugins') + '/list',function(data){
|
||||
var plugins = data.plugins || {};
|
||||
var pluginNames = Object.keys(plugins)
|
||||
var disconnectedPlugins = chosenDetectors.filter(item => !pluginNames.includes(item));
|
||||
|
@ -594,6 +596,9 @@ function getPluginsList(monitorConfig){
|
|||
});
|
||||
detectorsSelected.html(html)
|
||||
resolve(plugins)
|
||||
}).fail((err) => {
|
||||
console.error(err);
|
||||
resolve({})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -827,6 +832,13 @@ editorForm.submit(function(e){
|
|||
}
|
||||
debugLog(d)
|
||||
setSubmitButton(editorForm, lang.Save, `check`, false)
|
||||
}).fail((err) => {
|
||||
new PNotify({
|
||||
title: lang['Action Failed'],
|
||||
text: JSON.stringify(err, null, 3),
|
||||
type: 'danger'
|
||||
})
|
||||
setSubmitButton(editorForm, lang.Save, `check`, false)
|
||||
})
|
||||
//
|
||||
if(copySettingsSelector.val() === '1'){
|
||||
|
|
|
@ -646,7 +646,7 @@ function launchImportMonitorWindow(callback){
|
|||
reader.readAsText(f);
|
||||
});
|
||||
}
|
||||
function readAlertNotice(title, text, type) {
|
||||
function redAlertNotify({ title, text, type }) {
|
||||
var redAlertNotice = redAlertNotices[title];
|
||||
if (redAlertNotice) {
|
||||
redAlertNotice.update({
|
||||
|
@ -669,29 +669,6 @@ function readAlertNotice(title, text, type) {
|
|||
});
|
||||
}
|
||||
}
|
||||
function redAlertNotify(options) {
|
||||
var title = options.title;
|
||||
var redAlertNotice = redAlertNotices[title];
|
||||
var notifyOptions = {
|
||||
title: title,
|
||||
text: options.text,
|
||||
type: options.type,
|
||||
hide: options.hide === undefined ? false : options.hide,
|
||||
delay: options.delay || 30000
|
||||
};
|
||||
try{
|
||||
if (redAlertNotice) {
|
||||
redAlertNotice.update(notifyOptions);
|
||||
} else {
|
||||
redAlertNotices[title] = new PNotify(notifyOptions);
|
||||
redAlertNotices[title].on('close', function() {
|
||||
redAlertNotices[title] = null;
|
||||
});
|
||||
}
|
||||
}catch(err){
|
||||
console.error('redAlertNotify ERROR',err)
|
||||
}
|
||||
}
|
||||
function buildPosePoints(bodyParts, x, y){
|
||||
let theArray = []
|
||||
for(const point of bodyParts){
|
||||
|
@ -760,7 +737,11 @@ function drawMatrices(event, options, autoRemoveTimeout, drawTrails){
|
|||
}
|
||||
if(matrix.redAlert){
|
||||
var monitor = loadedMonitors[monitorId]
|
||||
readAlertNotice(`${monitor.name}`,`${matrix.tag} (${matrix.id})<br>${matrix.notice}`,'danger');
|
||||
redAlertNotify({
|
||||
title: `${monitor.name}`,
|
||||
text: `${matrix.tag} (${matrix.id})<br>${matrix.notice}`,
|
||||
type: 'danger'
|
||||
});
|
||||
}
|
||||
html += '</div>'
|
||||
}
|
||||
|
|
|
@ -152,7 +152,9 @@ $(document).ready(function(e){
|
|||
var newMon = mergeDeep(generateDefaultMonitorSettings(),monitorToPost)
|
||||
$.post(getApiPrefix(`configureMonitor`) + '/' + monitorToPost.mid,{data:JSON.stringify(newMon,null,3)},function(d){
|
||||
debugLog(d)
|
||||
})
|
||||
}).fail(function(xhr, status, error) {
|
||||
console.error(error)
|
||||
});
|
||||
}
|
||||
function loadLocalOptions(){
|
||||
var currentOptions = dashboardOptions()
|
||||
|
|
|
@ -94,7 +94,11 @@ $(document).ready(function(){
|
|||
liveStamp()
|
||||
})
|
||||
});
|
||||
var loadedOnce = false;
|
||||
onDashboardReady(function(){
|
||||
if(loadedOnce)return;
|
||||
loadedOnce = true;
|
||||
openTab('initial');
|
||||
drawMonitorListToSelector(monitorList.find('optgroup'))
|
||||
loadVideos({
|
||||
limit: 0,
|
||||
|
|
|
@ -238,8 +238,6 @@ onWebSocketEvent(function(d){
|
|||
case'monitor_snapshot':
|
||||
setTimeout(function(){
|
||||
var snapElement = $(`[data-mid="${d.mid}"] .snapshot`)
|
||||
console.log(d)
|
||||
console.log(snapElement)
|
||||
switch(d.snapshot_format){
|
||||
case'plc':
|
||||
snapElement.attr('src',placeholder.getData(placeholder.plcimg({text:d.snapshot.toUpperCase().split('').join(' '), fsize: 25, bgcolor:'#1462a5'})))
|
||||
|
|
|
@ -9,6 +9,7 @@ $(document).ready(function(){
|
|||
var dateSelector = $('#timeline-date-selector');
|
||||
var sideMenuList = $(`#side-menu-link-timeline ul`)
|
||||
var monitorList = $(`#timeline-monitor-list`)
|
||||
var videoTypeSelector = $(`#timeline-video-type`)
|
||||
var playToggles = timeStripControls.find('[timeline-action="playpause"]')
|
||||
var speedButtons = timeStripControls.find('[timeline-action="speed"]')
|
||||
var gridSizeButtons = timeStripControls.find('[timeline-action="gridSize"]')
|
||||
|
@ -106,8 +107,13 @@ $(document).ready(function(){
|
|||
}
|
||||
async function getVideosInGaps(gaps,monitorIds){
|
||||
var searchQuery = timeStripObjectSearchInput.val()
|
||||
var videoType = videoTypeSelector.val()
|
||||
var wantArchivedVideo = videoType === 'archive'
|
||||
var wantCloudVideo = videoType === 'cloud' && !wantArchivedVideo
|
||||
var andOnly = searchQuery.startsWith('and:')
|
||||
var videos = []
|
||||
var eventLimit = Object.values(loadedMonitors).length * 300
|
||||
searchQuery = andOnly ? searchQuery.replace('and:','') : searchQuery;
|
||||
async function loopOnGaps(monitorId){
|
||||
for (let i = 0; i < gaps.length; i++) {
|
||||
var range = gaps[i]
|
||||
|
@ -117,8 +123,9 @@ $(document).ready(function(){
|
|||
endDate: range[1],
|
||||
eventLimit,
|
||||
searchQuery,
|
||||
// archived: false,
|
||||
// customVideoSet: wantCloudVideo ? 'cloudVideos' : null,
|
||||
andOnly: andOnly ? '1' : '0',
|
||||
archived: wantArchivedVideo,
|
||||
customVideoSet: wantCloudVideo ? 'cloudVideos' : null,
|
||||
},null,dontShowDetectionOnTimeline)).videos;
|
||||
videos.push(...videosFound);
|
||||
executeExtender('timelineGetVideosByMonitor', [monitorId, videosFound])
|
||||
|
@ -460,7 +467,7 @@ $(document).ready(function(){
|
|||
function setVideoInCanvas(newVideo){
|
||||
var monitorId = newVideo.mid
|
||||
var container = getVideoContainerInCanvas(newVideo)
|
||||
.removeClass('no-video').find('.film').html(`<video muted src="${getApiPrefix('videos')}/${monitorId}/${newVideo.filename}"></video>`)
|
||||
.removeClass('no-video').find('.film').html(`<video muted src="${newVideo.href}"></video>`)
|
||||
var vidEl = getVideoElInCanvas(newVideo)
|
||||
var objectContainer = getObjectContainerInCanvas(newVideo)
|
||||
vidEl.playbackRate = timelineSpeed
|
||||
|
@ -1055,6 +1062,9 @@ $(document).ready(function(){
|
|||
timeStripObjectSearchInput.change(function(){
|
||||
refreshTimeline()
|
||||
})
|
||||
videoTypeSelector.change(function(){
|
||||
refreshTimeline()
|
||||
})
|
||||
timeStripVideoCanvas.on('dblclick','.timeline-video',function(){
|
||||
var monitorId = $(this).attr('data-mid')
|
||||
var video = loadedVideosOnCanvas[monitorId];
|
||||
|
|
|
@ -445,6 +445,7 @@ function loadEventsData(videoEvents){
|
|||
function getVideoSearchRequestQueries(options){
|
||||
var searchQuery = options.searchQuery
|
||||
var requestQueries = []
|
||||
var andOnly = options.andOnly === '1'
|
||||
var monitorId = options.monitorId
|
||||
var archived = options.archived
|
||||
var customVideoSet = options.customVideoSet
|
||||
|
@ -467,8 +468,12 @@ function getVideoSearchRequestQueries(options){
|
|||
if(archived){
|
||||
requestQueries.push(`archived=1`)
|
||||
}
|
||||
if(andOnly){
|
||||
requestQueries.push(`andOnly=1`)
|
||||
}
|
||||
return {
|
||||
searchQuery,
|
||||
andOnly,
|
||||
monitorId,
|
||||
archived,
|
||||
customVideoSet,
|
||||
|
@ -483,6 +488,7 @@ function getVideoSearchRequestQueries(options){
|
|||
function mergeVideosAndBin(options,callback){
|
||||
const {
|
||||
searchQuery,
|
||||
andOnly,
|
||||
monitorId,
|
||||
archived,
|
||||
customVideoSet,
|
||||
|
@ -520,6 +526,7 @@ function getVideos(options,callback,noEvents){
|
|||
options = options ? options : {}
|
||||
const {
|
||||
searchQuery,
|
||||
andOnly,
|
||||
monitorId,
|
||||
archived,
|
||||
customVideoSet,
|
||||
|
@ -533,7 +540,7 @@ function getVideos(options,callback,noEvents){
|
|||
$.getJSON(`${getApiPrefix(customVideoSet ? customVideoSet : searchQuery ? `videosByEventTag` : `videos`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([limit ? `limit=${limit}` : `noLimit=1`]).join('&')}`,function(data){
|
||||
var videos = data.videos.map((video) => {
|
||||
return Object.assign({},video,{
|
||||
href: getFullOrigin(true) + video.href
|
||||
href: `${getFullOrigin(true) + video.href}${customVideoSet === 'cloudVideos' ? `?type=${video.type}` : ''}`
|
||||
})
|
||||
})
|
||||
$.getJSON(`${getApiPrefix(`timelapse`)}${monitorId ? `/${monitorId}` : ''}?${requestQueries.concat([`noLimit=1`]).join('&')}`,function(timelapseFrames){
|
||||
|
|
|
@ -5,6 +5,7 @@ $(document).ready(function(e){
|
|||
var videosTableDrawArea = $('#videosTable_draw_area')
|
||||
var videosTablePreviewArea = $('#videosTable_preview_area')
|
||||
var objectTagSearchField = $('#videosTable_tag_search')
|
||||
var objectTagSearchFieldAndOnly = $('#videosTable_tag_search_andonly')
|
||||
var cloudVideoCheckSwitch = $('#videosTable_cloudVideos')
|
||||
var sideLinkListBox = $('#side-menu-link-videosTableView ul')
|
||||
var loadedVideosTable = [];
|
||||
|
@ -28,18 +29,18 @@ $(document).ready(function(e){
|
|||
}
|
||||
//Lazy Load Thumbnails
|
||||
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 imgBlock = el.find('.video-thumbnail-img-block');
|
||||
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 imgBlock = el.find('.video-thumbnail-img-block');
|
||||
|
||||
if (el.is(':visible')) { // Only load if visible
|
||||
const href = await getSnapshotFromVideoTimeFrame(monitorId, startDate, endDate);
|
||||
imgBlock.find('img').attr('src', href);
|
||||
}
|
||||
});
|
||||
if (el.is(':visible')) { // Only load if visible
|
||||
const href = await getSnapshotFromVideoTimeFrame(monitorId, startDate, endDate);
|
||||
imgBlock.find('img').attr('src', href);
|
||||
}
|
||||
});
|
||||
}
|
||||
window.openVideosTableView = function(monitorId){
|
||||
drawMonitorListToSelector(monitorsList,null,null,true)
|
||||
|
@ -54,32 +55,29 @@ $(document).ready(function(e){
|
|||
}
|
||||
})
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function(...args) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||
let timeout;
|
||||
return function(...args) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func.apply(this, args), wait);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
monitorsList.change(debounce(function(){
|
||||
videosTableDrawArea.bootstrapTable('destroy');
|
||||
drawVideosTableViewElements();
|
||||
}, 300));
|
||||
|
||||
objectTagSearchField.change(debounce(function(){
|
||||
videosTableDrawArea.bootstrapTable('destroy');
|
||||
drawVideosTableViewElements();
|
||||
}, 300));
|
||||
|
||||
cloudVideoCheckSwitch.change(debounce(function(){
|
||||
videosTableDrawArea.bootstrapTable('destroy');
|
||||
drawVideosTableViewElements();
|
||||
}, 300));
|
||||
[
|
||||
monitorsList,
|
||||
objectTagSearchField,
|
||||
objectTagSearchFieldAndOnly,
|
||||
cloudVideoCheckSwitch,
|
||||
].forEach(function(theElement){
|
||||
theElement.change(debounce(function(){
|
||||
videosTableDrawArea.bootstrapTable('destroy');
|
||||
drawVideosTableViewElements();
|
||||
}, 300));
|
||||
});
|
||||
|
||||
// Event listener for the Refresh link styled as a button
|
||||
$('.refresh-data').click(function(e) {
|
||||
e.preventDefault();
|
||||
videosTableDrawArea.bootstrapTable('destroy');
|
||||
videosTableDrawArea.bootstrapTable('destroy');
|
||||
drawVideosTableViewElements();
|
||||
});
|
||||
|
||||
|
@ -90,18 +88,19 @@ $(document).ready(function(e){
|
|||
|
||||
var dateRange = getSelectedTime(dateSelector);
|
||||
var searchQuery = objectTagSearchField.val() || null;
|
||||
var andOnly = objectTagSearchFieldAndOnly.val() || '0';
|
||||
var startDate = dateRange.startDate;
|
||||
var endDate = dateRange.endDate;
|
||||
var monitorId = monitorsList.val();
|
||||
var wantsArchivedVideo = getVideoSetSelected() === 'archive';
|
||||
var wantCloudVideo = wantCloudVideos();
|
||||
|
||||
if (!usePreloadedData) {
|
||||
var result = await getVideos({
|
||||
monitorId,
|
||||
startDate,
|
||||
endDate,
|
||||
searchQuery,
|
||||
andOnly,
|
||||
archived: wantsArchivedVideo,
|
||||
customVideoSet: wantCloudVideo ? 'cloudVideos' : null,
|
||||
pageSize: pageSize, // Pass pageSize to server
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
var wallViewCycleTimer = null;
|
||||
var cycleLiveOptionsBefore = null;
|
||||
var cycleLiveOptions = null;
|
||||
var cycleLiveMoveNext = function(){}
|
||||
var cycleLiveMovePrev = function(){}
|
||||
var cycleLiveFullList = null
|
||||
var cycleLiveCurrentPart = null
|
||||
function getRunningMonitors(asArray){
|
||||
const foundMonitors = {}
|
||||
$.each(loadedMonitors,function(monitorId,monitor){
|
||||
if(
|
||||
monitor.mode === 'start' ||
|
||||
monitor.mode === 'record'
|
||||
){
|
||||
foundMonitors[monitorId] = monitor
|
||||
}
|
||||
})
|
||||
return asArray ? Object.values(foundMonitors) : foundMonitors
|
||||
}
|
||||
function getListOfMonitorsToCycleLive(chosenTags,useMonitorIds){
|
||||
var monitors = []
|
||||
if(useMonitorIds){
|
||||
monitors = getMonitorsFromIds(chosenTags)
|
||||
}else if(chosenTags){
|
||||
var tags = sanitizeTagList(chosenTags)
|
||||
monitors = getMonitorsFromTags(tags)
|
||||
}else{
|
||||
monitors = getRunningMonitors(true)
|
||||
}
|
||||
return monitors;
|
||||
}
|
||||
function getPartForCycleLive(fullList, afterMonitorId, numberOfMonitors) {
|
||||
const startIndex = afterMonitorId ? fullList.findIndex(monitor => monitor.mid === afterMonitorId) : -1;
|
||||
const result = [];
|
||||
for (let i = 1; i <= numberOfMonitors; i++) {
|
||||
const index = (startIndex + i) % fullList.length;
|
||||
result.push(fullList[index]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function displayCycleSetOnLiveGrid(monitorsList){
|
||||
cycleLiveCurrentPart = [].concat(monitorsList)
|
||||
closeAllMonitors()
|
||||
openMonitors(monitorsList.map(monitor => monitor.mid))
|
||||
}
|
||||
// rotator
|
||||
function stopCycleLive(){
|
||||
clearTimeout(wallViewCycleTimer)
|
||||
wallViewCycleTimer = null
|
||||
}
|
||||
function resumeCycleLive(fullList,partForCycle,numberOfMonitors){
|
||||
const theLocalStorage = dashboardOptions()
|
||||
const cycleLiveTimerAmount = parseInt(theLocalStorage.cycleLiveTimerAmount) || 30000
|
||||
function next(){
|
||||
var afterMonitorId = partForCycle.slice(-1)[0].mid;
|
||||
partForCycle = getPartForCycleLive(fullList,afterMonitorId,numberOfMonitors)
|
||||
displayCycleSetOnLiveGrid(partForCycle)
|
||||
reset()
|
||||
}
|
||||
function prev(){
|
||||
var firstInPart = partForCycle[0].mid;
|
||||
var firstPartIndex = fullList.findIndex(monitor => monitor.mid === firstInPart)
|
||||
var backedToIndex = (firstPartIndex - (numberOfMonitors + 1) + fullList.length) % fullList.length;
|
||||
var beforeMonitorId = fullList[backedToIndex].mid
|
||||
partForCycle = getPartForCycleLive(fullList,beforeMonitorId,numberOfMonitors, true)
|
||||
displayCycleSetOnLiveGrid(partForCycle)
|
||||
reset()
|
||||
}
|
||||
function reset(){
|
||||
clearTimeout(wallViewCycleTimer)
|
||||
wallViewCycleTimer = setTimeout(function(){
|
||||
next()
|
||||
},cycleLiveTimerAmount)
|
||||
}
|
||||
reset()
|
||||
cycleLiveMoveNext = next
|
||||
cycleLiveMovePrev = prev
|
||||
}
|
||||
function beginCycleLive({
|
||||
chosenTags,
|
||||
useMonitorIds,
|
||||
numberOfMonitors,
|
||||
monitorHeight,
|
||||
}){
|
||||
var fullList = getListOfMonitorsToCycleLive(chosenTags,useMonitorIds)
|
||||
var partForCycle = getPartForCycleLive(fullList,null,numberOfMonitors)
|
||||
cycleLiveFullList = [].concat(fullList)
|
||||
displayCycleSetOnLiveGrid(partForCycle)
|
||||
stopCycleLive()
|
||||
resumeCycleLive(fullList,partForCycle,numberOfMonitors)
|
||||
}
|
||||
function toggleCycleLiveState(toggleState){
|
||||
console.log('Toggle Cycle', toggleState)
|
||||
const cycleControls = $('.wallview-cycle-control')
|
||||
if(toggleState){
|
||||
cycleControls.hide()
|
||||
cycleLiveOptions = null
|
||||
cycleLiveOptionsBefore = null
|
||||
stopCycleLive()
|
||||
}else{
|
||||
cycleControls.show()
|
||||
cycleLiveOptionsBefore = cycleLiveOptions ? Object.assign({},cycleLiveOptions) : null
|
||||
const theLocalStorage = dashboardOptions()
|
||||
const cycleLivePerRow = parseInt(theLocalStorage.cycleLivePerRow) || 2
|
||||
const cycleLiveNumberOfMonitors = parseInt(theLocalStorage.cycleLiveNumberOfMonitors) || 4
|
||||
const cycleLiveMonitorHeight = parseInt(theLocalStorage.cycleLiveMonitorHeight) || 4
|
||||
cycleLiveOptions = {
|
||||
chosenTags: null,
|
||||
useMonitorIds: null,
|
||||
monitorsPerRow: cycleLivePerRow,
|
||||
numberOfMonitors: cycleLiveNumberOfMonitors,
|
||||
monitorHeight: cycleLiveMonitorHeight,
|
||||
}
|
||||
beginCycleLive(cycleLiveOptions)
|
||||
}
|
||||
}
|
||||
function keyShortcutsForCycleLive(enable) {
|
||||
function cleanup(){
|
||||
document.removeEventListener('keydown', keyShortcuts['cycleLive'].keydown);
|
||||
document.removeEventListener('keyup', keyShortcuts['cycleLive'].keyup);
|
||||
delete(keyShortcuts['cycleLive'])
|
||||
}
|
||||
if(enable){
|
||||
let isKeyPressed = false;
|
||||
function handleKeyboard(event){
|
||||
if (isKeyPressed) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
switch(event.code){
|
||||
case 'Space':
|
||||
isKeyPressed = true;
|
||||
if(wallViewCycleTimer){
|
||||
stopCycleLive()
|
||||
}else{
|
||||
resumeCycleLive(
|
||||
cycleLiveFullList,
|
||||
cycleLiveCurrentPart,
|
||||
cycleLiveOptions.numberOfMonitors
|
||||
)
|
||||
}
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
isKeyPressed = true;
|
||||
cycleLiveMovePrev();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
isKeyPressed = true;
|
||||
cycleLiveMoveNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
function handleKeyup(event) {
|
||||
isKeyPressed = false;
|
||||
}
|
||||
keyShortcuts['cycleLive'] = {
|
||||
keydown: handleKeyboard,
|
||||
keyup: handleKeyup,
|
||||
}
|
||||
document.addEventListener('keydown', keyShortcuts['cycleLive'].keydown);
|
||||
document.addEventListener('keyup', keyShortcuts['cycleLive'].keyup);
|
||||
}else{
|
||||
cleanup()
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
$('body')
|
||||
.on('click', '.wallview-cycle', function(e){
|
||||
toggleCycleLiveState(!!wallViewCycleTimer)
|
||||
})
|
||||
.on('click', '.wallview-cycle-back', function(e){
|
||||
cycleLiveMovePrev();
|
||||
})
|
||||
.on('click', '.wallview-cycle-front', function(e){
|
||||
cycleLiveMoveNext();
|
||||
})
|
||||
})
|
|
@ -0,0 +1,408 @@
|
|||
$(document).ready(function() {
|
||||
var selectedController = 0;
|
||||
var keyLegend = {
|
||||
"0": "b",
|
||||
"1": "a",
|
||||
"2": "y",
|
||||
"3": "x",
|
||||
"4": "l",
|
||||
"5": "r",
|
||||
"6": "zl",
|
||||
"7": "zr",
|
||||
"8": "minus",
|
||||
"9": "plus",
|
||||
"10": "l_stick",
|
||||
"11": "r_stick",
|
||||
"12": "up",
|
||||
"13": "down",
|
||||
"14": "left",
|
||||
"15": "right",
|
||||
}
|
||||
var lastState = {
|
||||
sticks: {
|
||||
left: {},
|
||||
right: {},
|
||||
}
|
||||
}
|
||||
var lastPtzDirection = {}
|
||||
var buttonsPressed = {}
|
||||
var hasGP = false;
|
||||
var repGP;
|
||||
var reportInterval = 200
|
||||
var stickBase = 2048
|
||||
var stickMax = 4096
|
||||
var deadZoneThreshold = 0.35
|
||||
var outerDeadZone = 1.01
|
||||
var selectedMonitor = dashboardOptions().gamepadMonitorSelection;
|
||||
var monitorKeys = {};
|
||||
var sequenceButtonPressList = []
|
||||
var sequenceButtonPressTimeout = null
|
||||
var buttonPressAction = null;
|
||||
|
||||
function runPtzCommand(monitorId,switchChosen){
|
||||
switch(switchChosen){
|
||||
case'setHome':
|
||||
$.getJSON(getApiPrefix(`control`) + '/' + monitorId + '/setHome',function(data){
|
||||
console.log(data)
|
||||
})
|
||||
break;
|
||||
default:
|
||||
mainSocket.f({
|
||||
f: 'control',
|
||||
direction: switchChosen,
|
||||
id: monitorId,
|
||||
ke: $user.ke
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function runPtzMove(monitorId,switchChosen,doMove){
|
||||
mainSocket.f({
|
||||
f: doMove ? 'startMove' : 'stopMove',
|
||||
direction: switchChosen,
|
||||
id: monitorId,
|
||||
ke: $user.ke
|
||||
})
|
||||
}
|
||||
|
||||
window.setGamepadMonitorSelection = (monitorId) => {
|
||||
dashboardOptions('gamepadMonitorSelection', monitorId);
|
||||
selectedMonitor = `${monitorId}`;
|
||||
}
|
||||
|
||||
function canGame() {
|
||||
return "getGamepads" in navigator;
|
||||
}
|
||||
|
||||
function convertStickAxisTo2048(value){
|
||||
var newVal = parseInt((stickMax - stickBase) * value + stickBase)
|
||||
return newVal
|
||||
}
|
||||
|
||||
function getAnalogStickValues(gp, i, callback){
|
||||
var label = i === 0 ? 'left' : 'right'
|
||||
var horizontal = gp.axes[i] * outerDeadZone
|
||||
var vertical = gp.axes[i + 1] * outerDeadZone
|
||||
var newH = convertStickAxisTo2048(horizontal > deadZoneThreshold || horizontal < -deadZoneThreshold ? horizontal : 0)
|
||||
var newV = convertStickAxisTo2048((vertical > deadZoneThreshold || vertical < -deadZoneThreshold ? vertical : 0) * -1)
|
||||
if(
|
||||
newH !== lastState.sticks[label].h ||
|
||||
newV !== lastState.sticks[label].v
|
||||
){
|
||||
callback(label, newH, newV)
|
||||
}
|
||||
lastState.sticks[label].h = newH
|
||||
lastState.sticks[label].v = newV
|
||||
}
|
||||
|
||||
function getStickValue(gp, i, callback){
|
||||
var label = `axis${axis}`;
|
||||
var axis = gp.axes[i] * outerDeadZone
|
||||
var newH = convertStickAxisTo2048(axis > deadZoneThreshold || axis < -deadZoneThreshold ? axis : 0)
|
||||
if(newH !== lastState[label]){
|
||||
callback(newH)
|
||||
}
|
||||
lastState[label] = newH
|
||||
}
|
||||
|
||||
function getButtonsPressed(gp, callback, offCallback = () => {}){
|
||||
$.each(keyLegend,function(code,key){
|
||||
if(gp.buttons[code] && gp.buttons[code].pressed){
|
||||
if(!lastState[key]){
|
||||
buttonsPressed[code] = true;
|
||||
callback(code)
|
||||
}
|
||||
lastState[key] = true
|
||||
}else{
|
||||
if(lastState[key]){
|
||||
buttonsPressed[code] = false;
|
||||
offCallback(code)
|
||||
}
|
||||
lastState[key] = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function fullScreenInit(target){
|
||||
if (target.requestFullscreen) {
|
||||
target.requestFullscreen();
|
||||
} else if (target.mozRequestFullScreen) {
|
||||
target.mozRequestFullScreen();
|
||||
} else if (target.webkitRequestFullscreen) {
|
||||
target.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
|
||||
}
|
||||
}
|
||||
|
||||
function setCameraFromButtonCode(buttonCode = 0, preAdded){
|
||||
const addedOneToButtonCode = preAdded ? buttonCode : parseInt(buttonCode) + 1
|
||||
try{
|
||||
console.log('addedOneToButtonCode', addedOneToButtonCode)
|
||||
console.log('monitorKeys', monitorKeys)
|
||||
const monitor = loadedMonitors[monitorKeys[addedOneToButtonCode]];
|
||||
console.log('monitor', monitor)
|
||||
const monitorId = monitor.mid;
|
||||
const isFullscreened = !!document.fullscreenElement;
|
||||
if(isFullscreened) {
|
||||
document.exitFullscreen()
|
||||
}
|
||||
closeAllMonitors()
|
||||
openMonitorInLiveGrid(monitorId)
|
||||
if(isFullscreened) {
|
||||
const streamElement = $(`[live-stream="${monitorId}"]`)[0];
|
||||
fullScreenInit(streamElement)
|
||||
}
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
new PNotify({
|
||||
title: lang['Invalid Action'],
|
||||
text: `${lang.ptzControlIdNotFound}<br><br>${lang['Button Code']} : ${addedOneToButtonCode}`,
|
||||
type: 'warning'
|
||||
});
|
||||
console.log('No Monitor Associated :', buttonCode)
|
||||
}
|
||||
}
|
||||
|
||||
function openMonitorInLiveGrid(monitorId, callback){
|
||||
lastPtzDirection = {};
|
||||
setGamepadMonitorSelection(monitorId)
|
||||
selectMonitor(monitorId)
|
||||
displayInfoScreen()
|
||||
autoPlaceCurrentMonitorItems()
|
||||
saveLayout()
|
||||
}
|
||||
|
||||
function sendPtzCommand(direction, doMove){
|
||||
runPtzMove(selectedMonitor, direction, doMove)
|
||||
}
|
||||
|
||||
function sentPtzToHome(){
|
||||
runPtzCommand(selectedMonitor, 'center')
|
||||
}
|
||||
|
||||
function translatePointTiltStick(x, y){
|
||||
if(x > stickBase && !lastPtzDirection['right']){
|
||||
lastPtzDirection['right'] = true
|
||||
lastPtzDirection['left'] = false
|
||||
// sendPtzCommand('left', false)
|
||||
sendPtzCommand('right', true)
|
||||
}else if(x < stickBase && !lastPtzDirection['left']){
|
||||
lastPtzDirection['left'] = true
|
||||
lastPtzDirection['right'] = false
|
||||
// sendPtzCommand('right', false)
|
||||
sendPtzCommand('left', true)
|
||||
}else if(x === stickBase){
|
||||
if(lastPtzDirection['right'])sendPtzCommand('right', false)
|
||||
if(lastPtzDirection['left'])sendPtzCommand('left', false)
|
||||
lastPtzDirection['right'] = false
|
||||
lastPtzDirection['left'] = false
|
||||
}
|
||||
if(y > stickBase && !lastPtzDirection['up']){
|
||||
lastPtzDirection['up'] = true
|
||||
lastPtzDirection['down'] = false
|
||||
// sendPtzCommand('down', false)
|
||||
sendPtzCommand('up', true)
|
||||
}else if(y < stickBase && !lastPtzDirection['down']){
|
||||
lastPtzDirection['down'] = true
|
||||
lastPtzDirection['up'] = false
|
||||
// sendPtzCommand('up', false)
|
||||
sendPtzCommand('down', true)
|
||||
}else if(y === stickBase){
|
||||
if(lastPtzDirection['up'])sendPtzCommand('up', false)
|
||||
if(lastPtzDirection['down'])sendPtzCommand('down', false)
|
||||
lastPtzDirection['down'] = false
|
||||
lastPtzDirection['up'] = false
|
||||
}
|
||||
}
|
||||
|
||||
function translateZoomAxis(value){
|
||||
if(value > stickBase && !lastPtzDirection['zoom_in']){
|
||||
lastPtzDirection['zoom_in'] = true
|
||||
lastPtzDirection['zoom_out'] = false
|
||||
// sendPtzCommand('zoom_out', false)
|
||||
sendPtzCommand('zoom_in', true)
|
||||
}else if(value < stickBase && !lastPtzDirection['zoom_out']){
|
||||
lastPtzDirection['zoom_out'] = true
|
||||
lastPtzDirection['zoom_in'] = false
|
||||
// sendPtzCommand('zoom_in', false)
|
||||
sendPtzCommand('zoom_out', true)
|
||||
}else if(value === stickBase){
|
||||
if(lastPtzDirection['zoom_in'])sendPtzCommand('zoom_in', false)
|
||||
if(lastPtzDirection['zoom_out'])sendPtzCommand('zoom_out', false)
|
||||
lastPtzDirection['zoom_in'] = false
|
||||
lastPtzDirection['zoom_out'] = false
|
||||
}
|
||||
}
|
||||
|
||||
function reportOnXboxGamepad() {
|
||||
try{
|
||||
var gp = navigator.getGamepads()[0];
|
||||
getButtonsPressed(gp, function(buttonCode){
|
||||
if(buttonCode == 6){
|
||||
sendPtzCommand('zoom_out', true)
|
||||
}else if(buttonCode == 7){
|
||||
sendPtzCommand('zoom_in', true)
|
||||
}else if(buttonCode == 8){
|
||||
// closeSnapshot()
|
||||
// openSnapshot()
|
||||
}else if(buttonCode == 9){
|
||||
sentPtzToHome()
|
||||
}else if(buttonCode == 11){
|
||||
if (!document.fullscreenElement) {
|
||||
fullScreenLiveGridStreamById(selectedMonitor)
|
||||
}else{
|
||||
document.exitFullscreen()
|
||||
}
|
||||
}else if(buttonCode == 12){
|
||||
toggleCycleLiveState(!!wallViewCycleTimer)
|
||||
}else if(buttonCode == 14){
|
||||
cycleLiveMovePrev();
|
||||
}else if(buttonCode == 15){
|
||||
cycleLiveMoveNext();
|
||||
}else{
|
||||
buttonPressAction(buttonCode)
|
||||
}
|
||||
}, function(buttonCode){
|
||||
if(buttonCode == 6){
|
||||
sendPtzCommand('zoom_out', false)
|
||||
}else if(buttonCode == 7){
|
||||
sendPtzCommand('zoom_in', false)
|
||||
}
|
||||
})
|
||||
getAnalogStickValues(gp, 0, function(stick, x, y){
|
||||
translatePointTiltStick(x, y)
|
||||
})
|
||||
getAnalogStickValues(gp, 2, function(stick, x, y){
|
||||
translateZoomAxis(y)
|
||||
})
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
// stopReporting()
|
||||
}
|
||||
}
|
||||
|
||||
function reportOnGenericGamepad() {
|
||||
try{
|
||||
const gp = navigator.getGamepads()[0];
|
||||
getButtonsPressed(gp, function(buttonCode){
|
||||
if(buttonCode == 10){
|
||||
// closeSnapshot()
|
||||
}else if(buttonCode == 11){
|
||||
// closeSnapshot()
|
||||
// openSnapshot()
|
||||
}else{
|
||||
buttonPressAction(buttonCode)
|
||||
}
|
||||
},function(buttonCode){
|
||||
|
||||
})
|
||||
getAnalogStickValues(gp, 0, function(stick, x, y){
|
||||
translatePointTiltStick(x, y)
|
||||
})
|
||||
getStickValue(gp, 2,function(value){
|
||||
translateZoomAxis(value)
|
||||
})
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
// stopReporting()
|
||||
}
|
||||
}
|
||||
|
||||
function openSnapshot(){
|
||||
if(!$.confirm.e.is(':visible')){
|
||||
getSnapshot(loadedMonitors[selectedMonitor],function(url){
|
||||
$.confirm.create({
|
||||
title: lang.Snapshot,
|
||||
body: `<img src="${url}">`,
|
||||
clickOptions: {
|
||||
class: 'btn-primary',
|
||||
title: lang.Close,
|
||||
},
|
||||
clickCallback: async function(){}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
function closeSnapshot(){
|
||||
$.confirm.e.modal('hide')
|
||||
}
|
||||
|
||||
|
||||
function startReporting(){
|
||||
if(hasGP){
|
||||
console.log('Reading Gamepad')
|
||||
repGP = window.setInterval(reportOnGamepad, reportInterval);
|
||||
}
|
||||
}
|
||||
|
||||
function stopReporting(){
|
||||
console.log('Stopping Gamepad')
|
||||
window.clearInterval(repGP)
|
||||
}
|
||||
|
||||
function generateMonitorKeysFromPtzIds(){
|
||||
monitorKeys = []
|
||||
Object.values(loadedMonitors)
|
||||
.filter(item => !!parseInt(item.details.ptz_id))
|
||||
.sort((a, b) => parseInt(b.details.ptz_id) - parseInt(a.details.ptz_id))
|
||||
.forEach((item) => {
|
||||
console.log(item.details.ptz_id)
|
||||
monitorKeys[item.details.ptz_id] = item.mid;
|
||||
});
|
||||
console.log(monitorKeys)
|
||||
}
|
||||
|
||||
function setControllerType(gamepadId){
|
||||
switch(true){
|
||||
case gamepadId.includes('Xbox'):
|
||||
reportInterval = 200;
|
||||
reportOnGamepad = reportOnXboxGamepad
|
||||
buttonPressAction = setCameraFromButtonCode
|
||||
console.log('Xbox Controller found!')
|
||||
break;
|
||||
default:
|
||||
reportInterval = 50;
|
||||
reportOnGamepad = reportOnGenericGamepad
|
||||
buttonPressAction = sequenceButtonPress
|
||||
break;
|
||||
}
|
||||
}
|
||||
var reportOnGamepad = reportOnXboxGamepad;
|
||||
|
||||
function sequenceButtonPress(buttonCode){
|
||||
sequenceButtonPressList.push(buttonCode)
|
||||
clearTimeout(sequenceButtonPressTimeout)
|
||||
sequenceButtonPressTimeout = setTimeout(() => {
|
||||
const newButtonCode = parseInt(sequenceButtonPressList.map(item => `${parseInt(item) + 1}`).join(''))
|
||||
setCameraFromButtonCode(newButtonCode, true)
|
||||
sequenceButtonPressList = []
|
||||
},300)
|
||||
}
|
||||
|
||||
if(canGame()) {
|
||||
$(window).on("gamepadconnected", function(e) {
|
||||
hasGP = true;
|
||||
startReporting()
|
||||
const gamepadName = e.originalEvent.gamepad.id;
|
||||
setControllerType(gamepadName)
|
||||
console.log('Gamepad Connected!', gamepadName)
|
||||
})
|
||||
.on("gamepaddisconnected", function() {
|
||||
if(!navigator.getGamepads()[0]){
|
||||
hasGP = false;
|
||||
console.log('Gamepad Disconnected!')
|
||||
}
|
||||
})
|
||||
}
|
||||
addActionToExtender('onDashboardReady',function(){
|
||||
generateMonitorKeysFromPtzIds();
|
||||
startReporting()
|
||||
})
|
||||
// onWebSocketEvent(function(d){
|
||||
// switch(d.f){
|
||||
// case'monitor_edit':
|
||||
// generateMonitorKeysFromPtzIds();
|
||||
// break;
|
||||
// }
|
||||
// })
|
||||
});
|
|
@ -1,281 +1,345 @@
|
|||
var loadedMonitors = {}
|
||||
var selectedMonitors = {}
|
||||
$(document).ready(function(){
|
||||
PNotify.prototype.options.styling = "fontawesome";
|
||||
var wallViewMonitorList = $('#wallview-monitorList')
|
||||
var wallViewControls = $('#wallview-controls')
|
||||
var wallViewCanvas = $('#wallview-canvas')
|
||||
var wallViewInfoScreen = $('#wallview-info-screen')
|
||||
var theWindow = $(window);
|
||||
var lastWindowWidth = theWindow.width()
|
||||
var lastWindowHeight = theWindow.height()
|
||||
function getQueryString(){
|
||||
var theObject = {}
|
||||
location.search.substring(1).split('&').forEach(function(string){
|
||||
var parts = string.split('=')
|
||||
theObject[parts[0]] = parts[1]
|
||||
})
|
||||
return theObject
|
||||
PNotify.prototype.options.styling = "fontawesome";
|
||||
var wallViewMonitorList = $('#wallview-monitorList')
|
||||
var wallViewMonitorListContainer = $('#wallview-monitorList-container')
|
||||
var wallViewControls = $('#wallview-controls')
|
||||
var wallViewCanvas = $('#wallview-canvas')
|
||||
var wallViewInfoScreen = $('#wallview-info-screen')
|
||||
var theWindow = $(window);
|
||||
var lastWindowWidth = theWindow.width()
|
||||
var lastWindowHeight = theWindow.height()
|
||||
var websocketPath = checkCorrectPathEnding(urlPrefix.replace(location.origin, '')) + 'socket.io'
|
||||
function checkCorrectPathEnding(x){
|
||||
var length=x.length
|
||||
if(x.charAt(length-1)!=='/'){
|
||||
x=x+'/'
|
||||
}
|
||||
function featureIsActivated(showNotice){
|
||||
if(userHasSubscribed){
|
||||
return true
|
||||
}else{
|
||||
if(showNotice){
|
||||
new PNotify({
|
||||
title: lang.activationRequired,
|
||||
text: lang.featureRequiresActivationText,
|
||||
type: 'warning'
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
return x
|
||||
}
|
||||
function dashboardOptions(r,rr,rrr){
|
||||
if(!rrr){rrr={};};if(typeof rrr === 'string'){rrr={n:rrr}};if(!rrr.n){rrr.n='ShinobiOptions_'+location.host+'_'+$user.ke+$user.uid}
|
||||
ii={o:localStorage.getItem(rrr.n)};try{ii.o=JSON.parse(ii.o)}catch(e){ii.o={}}
|
||||
if(!ii.o){ii.o={}}
|
||||
if(r&&rr&&!rrr.x){
|
||||
ii.o[r]=rr;
|
||||
}
|
||||
function createWallViewWindow(windowName){
|
||||
var el = $(document)
|
||||
var width = el.width()
|
||||
var height = el.height()
|
||||
window.open(getApiPrefix() + '/wallview/' + groupKey + (windowName ? '?window=' + windowName : ''), 'wallview_'+windowName, 'height='+height+',width='+width)
|
||||
switch(rrr.x){
|
||||
case 0:
|
||||
delete(ii.o[r])
|
||||
break;
|
||||
case 1:
|
||||
delete(ii.o[r][rr])
|
||||
break;
|
||||
}
|
||||
function getApiPrefix(innerPart){
|
||||
return `${urlPrefix}${authKey}${innerPart ? `/${innerPart}/${groupKey}` : ''}`
|
||||
}
|
||||
function getWindowName(){
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const theWindowChoice = urlParams.get('window');
|
||||
return theWindowChoice || '1'
|
||||
}
|
||||
function drawMonitorListItem(monitor){
|
||||
wallViewMonitorList.append(`<li><a class="dropdown-item" select-monitor="${monitor.mid}" href="#"><i class="fa fa-check"></i> ${monitor.name}</a></li>`)
|
||||
}
|
||||
function drawMonitorList(){
|
||||
return new Promise((resolve) => {
|
||||
$.get(getApiPrefix('monitor'),function(monitors){
|
||||
$.each(monitors, function(n,monitor){
|
||||
if(monitor.mode !== 'stop' && monitor.mode !== 'idle'){
|
||||
loadedMonitors[monitor.mid] = monitor;
|
||||
drawMonitorListItem(monitor)
|
||||
}
|
||||
})
|
||||
resolve(monitors)
|
||||
localStorage.setItem(rrr.n,JSON.stringify(ii.o))
|
||||
return ii.o
|
||||
}
|
||||
function getQueryString(){
|
||||
var theObject = {}
|
||||
location.search.substring(1).split('&').forEach(function(string){
|
||||
var parts = string.split('=')
|
||||
theObject[parts[0]] = parts[1]
|
||||
})
|
||||
return theObject
|
||||
}
|
||||
function featureIsActivated(showNotice){
|
||||
if(userHasSubscribed){
|
||||
return true
|
||||
}else{
|
||||
if(showNotice){
|
||||
new PNotify({
|
||||
title: lang.activationRequired,
|
||||
text: lang.featureRequiresActivationText,
|
||||
type: 'warning'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getMonitorListItem(monitorId){
|
||||
return wallViewMonitorList.find(`[select-monitor="${monitorId}"]`)
|
||||
}
|
||||
|
||||
function selectMonitor(monitorId, css){
|
||||
css = css || {};
|
||||
var embedHost = getQueryString().host || `/`;
|
||||
var isSelected = selectedMonitors[monitorId]
|
||||
if(isSelected)return;
|
||||
var numberOfSelected = Object.keys(selectedMonitors)
|
||||
if(numberOfSelected > 3 && !featureIsActivated(true)){
|
||||
return
|
||||
}
|
||||
selectedMonitors[monitorId] = Object.assign({}, loadedMonitors[monitorId]);
|
||||
wallViewCanvas.append(`<div class="wallview-video p-0 m-0" live-stream="${monitorId}" style="left:${css.left || 0}px;top:${css.top || 0}px;width:${css.width ? css.width + 'px' : '50vw'};height:${css.height ? css.height + 'px' : '50vh'};"><div class="overlay"><div class="wallview-item-controls text-end"><a class="btn btn-sm btn-outline-danger wallview-item-close"><i class="fa fa-times"></i></a></div></div><iframe src="${getApiPrefix('embed')}/${monitorId}/fullscreen%7Cjquery%7Crelative?host=${embedHost}"></iframe></div>`)
|
||||
wallViewCanvas.find(`[live-stream="${monitorId}"]`)
|
||||
.draggable({
|
||||
grid: [40, 40],
|
||||
snap: '#wallview-canvas',
|
||||
containment: "window",
|
||||
stop: function(){
|
||||
saveLayout()
|
||||
}
|
||||
})
|
||||
.resizable({
|
||||
grid: [40, 40],
|
||||
snap: '#wallview-container',
|
||||
stop: function(){
|
||||
saveLayout()
|
||||
}
|
||||
});
|
||||
getMonitorListItem(monitorId).addClass('active')
|
||||
return false
|
||||
}
|
||||
function deselectMonitor(monitorId){
|
||||
delete(selectedMonitors[monitorId])
|
||||
var monitorItem = wallViewCanvas.find(`[live-stream="${monitorId}"]`);
|
||||
monitorItem.find('iframe').attr('src','about:blank')
|
||||
monitorItem.remove()
|
||||
getMonitorListItem(monitorId).removeClass('active')
|
||||
}
|
||||
|
||||
function getCurrentLayout(){
|
||||
var layout = []
|
||||
wallViewCanvas.find('.wallview-video').each(function(n,v){
|
||||
var el = $(v)
|
||||
var monitorId = el.attr('live-stream')
|
||||
var position = el.position()
|
||||
layout.push({
|
||||
monitorId,
|
||||
css: {
|
||||
left: position.left,
|
||||
top: position.top,
|
||||
width: el.width(),
|
||||
height: el.height(),
|
||||
}
|
||||
function createWallViewWindow(windowName){
|
||||
var el = $(document)
|
||||
var width = el.width()
|
||||
var height = el.height()
|
||||
window.open(getApiPrefix() + '/wallview/' + groupKey + (windowName ? '?window=' + windowName : ''), 'wallview_'+windowName, 'height='+height+',width='+width)
|
||||
}
|
||||
function getApiPrefix(innerPart){
|
||||
return `${urlPrefix}${authKey}${innerPart ? `/${innerPart}/${groupKey}` : ''}`
|
||||
}
|
||||
function getWindowName(){
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const theWindowChoice = urlParams.get('window');
|
||||
return theWindowChoice || '1'
|
||||
}
|
||||
function drawMonitorList(){
|
||||
return new Promise((resolve) => {
|
||||
$.get(getApiPrefix('monitor'),function(monitors){
|
||||
$.each(monitors, function(n,monitor){
|
||||
if(monitor.mode !== 'stop' && monitor.mode !== 'idle'){
|
||||
loadedMonitors[monitor.mid] = monitor;
|
||||
}
|
||||
})
|
||||
})
|
||||
return layout
|
||||
}
|
||||
|
||||
function saveLayout(){
|
||||
var windowName = getWindowName();
|
||||
var layouts = getAllLayouts();
|
||||
var layout = getCurrentLayout();
|
||||
var saveContainer = {
|
||||
layout,
|
||||
windowInnerWidth: window.innerWidth,
|
||||
windowInnerHeight: window.innerHeight,
|
||||
}
|
||||
layouts[windowName] = saveContainer;
|
||||
localStorage.setItem('windowLayouts', JSON.stringify(layouts));
|
||||
}
|
||||
|
||||
function getAllLayouts(){
|
||||
return JSON.parse(localStorage.getItem(`windowLayouts`) || '{}');
|
||||
}
|
||||
|
||||
function getLayout(full){
|
||||
var windowName = getWindowName();
|
||||
var saveContainer = getAllLayouts()[windowName]
|
||||
if(full)return saveContainer || { layout: [] };
|
||||
var layout = saveContainer.layout || []
|
||||
return layout;
|
||||
}
|
||||
|
||||
function resetWindowDimensions(){
|
||||
var saveContainer = getLayout(true);
|
||||
if(saveContainer.windowInnerWidth && saveContainer.windowInnerHeight){
|
||||
var widthDiff = window.outerWidth - window.innerWidth;
|
||||
var heightDiff = window.outerHeight - window.innerHeight;
|
||||
lastWindowWidth = saveContainer.windowInnerWidth
|
||||
lastWindowHeight = saveContainer.windowInnerHeight
|
||||
window.resizeTo(saveContainer.windowInnerWidth + widthDiff, saveContainer.windowInnerHeight + heightDiff);
|
||||
}
|
||||
}
|
||||
|
||||
function loadSavedLayout(){
|
||||
var saveContainer = getLayout(true);
|
||||
resetWindowDimensions()
|
||||
saveContainer.layout.forEach(function({ monitorId, css }, n){
|
||||
selectMonitor(monitorId, css);
|
||||
});
|
||||
displayInfoScreen();
|
||||
}
|
||||
|
||||
function displayInfoScreen(){
|
||||
if(getCurrentLayout().length === 0){
|
||||
wallViewInfoScreen.css('display','flex')
|
||||
}else{
|
||||
wallViewInfoScreen.hide()
|
||||
}
|
||||
}
|
||||
function resizeMonitorItem({ monitorId, css }, oldWidth, oldHeight, newWidth, newHeight){
|
||||
var monitorItem = wallViewCanvas.find(`[live-stream="${monitorId}"]`);
|
||||
var newCss = rescaleMatrix(css, oldWidth, oldHeight, newWidth, newHeight)
|
||||
monitorItem.css(newCss)
|
||||
}
|
||||
function rescaleMatrix(matrix, oldWidth, oldHeight, newWidth, newHeight) {
|
||||
const scaleX = newWidth / oldWidth;
|
||||
const scaleY = newHeight / oldHeight;
|
||||
|
||||
return {
|
||||
left: matrix.left * scaleX,
|
||||
top: matrix.top * scaleY,
|
||||
width: matrix.width * scaleX,
|
||||
height: matrix.height * scaleY
|
||||
};
|
||||
}
|
||||
|
||||
function onWindowResize(){
|
||||
var currentWindowWidth = theWindow.width()
|
||||
var currentWindowHeight = theWindow.height()
|
||||
var layout = getCurrentLayout();
|
||||
for(item of layout){
|
||||
resizeMonitorItem(item,lastWindowWidth,lastWindowHeight,currentWindowWidth,currentWindowHeight)
|
||||
}
|
||||
lastWindowWidth = currentWindowWidth
|
||||
lastWindowHeight = currentWindowHeight
|
||||
}
|
||||
|
||||
function autoPlaceCurrentMonitorItems() {
|
||||
const wallviewVideos = wallViewCanvas.find('.wallview-video');
|
||||
const totalItems = wallviewVideos.length;
|
||||
|
||||
let numRows, numCols;
|
||||
|
||||
if (totalItems === 6 || totalItems === 5) {
|
||||
numCols = 3;
|
||||
numRows = 2;
|
||||
} else {
|
||||
numRows = Math.ceil(Math.sqrt(totalItems));
|
||||
numCols = Math.ceil(totalItems / numRows);
|
||||
}
|
||||
|
||||
const containerWidth = wallViewCanvas.width();
|
||||
const containerHeight = wallViewCanvas.height();
|
||||
const itemWidth = containerWidth / numCols;
|
||||
const itemHeight = containerHeight / numRows;
|
||||
|
||||
wallviewVideos.each(function(index, element) {
|
||||
const row = Math.floor(index / numCols);
|
||||
const col = index % numCols;
|
||||
|
||||
$(element).css({
|
||||
left: col * itemWidth,
|
||||
top: row * itemHeight,
|
||||
width: itemWidth,
|
||||
height: itemHeight
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openAllMonitors(){
|
||||
$.each(loadedMonitors,function(monitorId, monitor){
|
||||
var modeAccepted = monitor.mode !== 'stop' && monitor.mode !== 'idle'
|
||||
if(modeAccepted)selectMonitor(monitorId)
|
||||
})
|
||||
autoPlaceCurrentMonitorItems()
|
||||
displayInfoScreen()
|
||||
saveLayout()
|
||||
}
|
||||
|
||||
function openNextMonitors(numberOf){
|
||||
var allLayouts = getAllLayouts()
|
||||
var ignoreMonitors = []
|
||||
var availableMonitors = []
|
||||
var numberToOpen = parseInt(numberOf) || 4;
|
||||
$.each(allLayouts,function(windowName, { layout }){
|
||||
$.each(layout,function(n, { monitorId }){
|
||||
ignoreMonitors.push(monitorId)
|
||||
var tags = getListOfTagsFromMonitors()
|
||||
var monitorsOrdered = Object.values(loadedMonitors).sort((a, b) => a.name.localeCompare(b.name));
|
||||
var allFound = [
|
||||
{
|
||||
attributes: `tag=""`,
|
||||
class: `cursor-pointer wallview-open-monitor-group`,
|
||||
color: 'forestgreen',
|
||||
label: lang['All Monitors'],
|
||||
}
|
||||
]
|
||||
$.each(tags,function(tag,monitors){
|
||||
allFound.push({
|
||||
attributes: `tag="${tag}"`,
|
||||
class: `cursor-pointer wallview-open-monitor-group`,
|
||||
color: 'blue',
|
||||
label: tag,
|
||||
})
|
||||
})
|
||||
});
|
||||
$.each(loadedMonitors,function(monitorId, monitor){
|
||||
if(ignoreMonitors.indexOf(monitor.mid) === -1){
|
||||
var modeAccepted = monitor.mode !== 'stop' && monitor.mode !== 'idle'
|
||||
if(modeAccepted)availableMonitors.push(monitorId)
|
||||
}
|
||||
});
|
||||
for (let i = 0; i < numberToOpen; i++) {
|
||||
selectMonitor(availableMonitors[i])
|
||||
}
|
||||
autoPlaceCurrentMonitorItems()
|
||||
displayInfoScreen()
|
||||
saveLayout()
|
||||
}
|
||||
|
||||
function closeAllMonitors(){
|
||||
$.each(loadedMonitors,function(monitorId, monitor){
|
||||
deselectMonitor(monitorId)
|
||||
$.each(monitorsOrdered,function(monitorKey,monitor){
|
||||
var monitorId = monitor.mid
|
||||
var label = monitor.name
|
||||
allFound.push({
|
||||
attributes: `select-monitor="${monitorId}"`,
|
||||
class: `cursor-pointer`,
|
||||
color: 'grey',
|
||||
label,
|
||||
})
|
||||
})
|
||||
var html = allFound.map(item => `<div class="mb-1 search-row"><a class="btn d-block btn-primary btn-sm ${item.class}" ${item.attributes} href="#">${item.label}</a></div>`).join('')
|
||||
wallViewMonitorList.html(html)
|
||||
resolve(monitors)
|
||||
})
|
||||
displayInfoScreen()
|
||||
saveLayout()
|
||||
})
|
||||
}
|
||||
|
||||
function getMonitorListItem(monitorId){
|
||||
return wallViewMonitorList.find(`[select-monitor="${monitorId}"]`)
|
||||
}
|
||||
|
||||
function selectMonitor(monitorId, css){
|
||||
css = css || {};
|
||||
var embedHost = getQueryString().host || `/`;
|
||||
var isSelected = selectedMonitors[monitorId]
|
||||
if(isSelected)return;
|
||||
var numberOfSelected = Object.keys(selectedMonitors)
|
||||
if(numberOfSelected > 3 && !featureIsActivated(true)){
|
||||
return
|
||||
}
|
||||
selectedMonitors[monitorId] = Object.assign({}, loadedMonitors[monitorId]);
|
||||
wallViewCanvas.append(`<div class="wallview-video p-0 m-0" live-stream="${monitorId}" style="left:${css.left || 0}px;top:${css.top || 0}px;width:${css.width ? css.width + 'px' : '50vw'};height:${css.height ? css.height + 'px' : '50vh'};"><div class="overlay"><div class="wallview-item-controls text-end"><a class="btn btn-sm btn-outline-danger wallview-item-close"><i class="fa fa-times"></i></a></div></div><iframe src="${getApiPrefix('embed')}/${monitorId}/fullscreen%7Cjquery%7Crelative?host=${embedHost}"></iframe></div>`)
|
||||
wallViewCanvas.find(`[live-stream="${monitorId}"]`)
|
||||
.draggable({
|
||||
grid: [40, 40],
|
||||
snap: '#wallview-canvas',
|
||||
containment: "window",
|
||||
stop: function(){
|
||||
saveLayout()
|
||||
}
|
||||
})
|
||||
.resizable({
|
||||
grid: [40, 40],
|
||||
snap: '#wallview-container',
|
||||
stop: function(){
|
||||
saveLayout()
|
||||
}
|
||||
});
|
||||
getMonitorListItem(monitorId).removeClass('btn-primary').addClass('btn-warning')
|
||||
}
|
||||
function deselectMonitor(monitorId){
|
||||
delete(selectedMonitors[monitorId])
|
||||
var monitorItem = wallViewCanvas.find(`[live-stream="${monitorId}"]`);
|
||||
monitorItem.find('iframe').attr('src','about:blank')
|
||||
monitorItem.remove()
|
||||
getMonitorListItem(monitorId).removeClass('btn-warning').addClass('btn-primary')
|
||||
}
|
||||
|
||||
function getCurrentLayout(){
|
||||
var layout = []
|
||||
wallViewCanvas.find('.wallview-video').each(function(n,v){
|
||||
var el = $(v)
|
||||
var monitorId = el.attr('live-stream')
|
||||
var position = el.position()
|
||||
layout.push({
|
||||
monitorId,
|
||||
css: {
|
||||
left: position.left,
|
||||
top: position.top,
|
||||
width: el.width(),
|
||||
height: el.height(),
|
||||
}
|
||||
})
|
||||
})
|
||||
return layout
|
||||
}
|
||||
|
||||
function saveLayout(){
|
||||
var windowName = getWindowName();
|
||||
var layouts = getAllLayouts();
|
||||
var layout = getCurrentLayout();
|
||||
var saveContainer = {
|
||||
layout,
|
||||
windowInnerWidth: window.innerWidth,
|
||||
windowInnerHeight: window.innerHeight,
|
||||
}
|
||||
layouts[windowName] = saveContainer;
|
||||
localStorage.setItem('windowLayouts', JSON.stringify(layouts));
|
||||
}
|
||||
|
||||
function getAllLayouts(){
|
||||
return JSON.parse(localStorage.getItem(`windowLayouts`) || '{}');
|
||||
}
|
||||
|
||||
function getLayout(full){
|
||||
var windowName = getWindowName();
|
||||
var saveContainer = getAllLayouts()[windowName]
|
||||
if(full)return saveContainer || { layout: [] };
|
||||
var layout = saveContainer.layout || []
|
||||
return layout;
|
||||
}
|
||||
|
||||
function resetWindowDimensions(){
|
||||
var saveContainer = getLayout(true);
|
||||
if(saveContainer.windowInnerWidth && saveContainer.windowInnerHeight){
|
||||
var widthDiff = window.outerWidth - window.innerWidth;
|
||||
var heightDiff = window.outerHeight - window.innerHeight;
|
||||
lastWindowWidth = saveContainer.windowInnerWidth
|
||||
lastWindowHeight = saveContainer.windowInnerHeight
|
||||
window.resizeTo(saveContainer.windowInnerWidth + widthDiff, saveContainer.windowInnerHeight + heightDiff);
|
||||
}
|
||||
}
|
||||
|
||||
function loadSavedLayout(){
|
||||
var saveContainer = getLayout(true);
|
||||
resetWindowDimensions()
|
||||
saveContainer.layout.forEach(function({ monitorId, css }, n){
|
||||
selectMonitor(monitorId, css);
|
||||
});
|
||||
displayInfoScreen();
|
||||
}
|
||||
|
||||
function displayInfoScreen(){
|
||||
if(getCurrentLayout().length === 0){
|
||||
wallViewInfoScreen.css('display','flex')
|
||||
}else{
|
||||
wallViewInfoScreen.hide()
|
||||
}
|
||||
}
|
||||
function resizeMonitorItem({ monitorId, css }, oldWidth, oldHeight, newWidth, newHeight){
|
||||
var monitorItem = wallViewCanvas.find(`[live-stream="${monitorId}"]`);
|
||||
var newCss = rescaleMatrix(css, oldWidth, oldHeight, newWidth, newHeight)
|
||||
monitorItem.css(newCss)
|
||||
}
|
||||
function rescaleMatrix(matrix, oldWidth, oldHeight, newWidth, newHeight) {
|
||||
const scaleX = newWidth / oldWidth;
|
||||
const scaleY = newHeight / oldHeight;
|
||||
|
||||
return {
|
||||
left: matrix.left * scaleX,
|
||||
top: matrix.top * scaleY,
|
||||
width: matrix.width * scaleX,
|
||||
height: matrix.height * scaleY
|
||||
};
|
||||
}
|
||||
|
||||
function onWindowResize(){
|
||||
var currentWindowWidth = theWindow.width()
|
||||
var currentWindowHeight = theWindow.height()
|
||||
var layout = getCurrentLayout();
|
||||
for(item of layout){
|
||||
resizeMonitorItem(item,lastWindowWidth,lastWindowHeight,currentWindowWidth,currentWindowHeight)
|
||||
}
|
||||
lastWindowWidth = currentWindowWidth
|
||||
lastWindowHeight = currentWindowHeight
|
||||
}
|
||||
|
||||
function autoPlaceCurrentMonitorItems() {
|
||||
const wallviewVideos = wallViewCanvas.find('.wallview-video');
|
||||
const totalItems = wallviewVideos.length;
|
||||
|
||||
let numRows, numCols;
|
||||
|
||||
if (totalItems === 6 || totalItems === 5) {
|
||||
numCols = 3;
|
||||
numRows = 2;
|
||||
} else {
|
||||
numRows = Math.ceil(Math.sqrt(totalItems));
|
||||
numCols = Math.ceil(totalItems / numRows);
|
||||
}
|
||||
|
||||
const containerWidth = wallViewCanvas.width();
|
||||
const containerHeight = wallViewCanvas.height();
|
||||
const itemWidth = containerWidth / numCols;
|
||||
const itemHeight = containerHeight / numRows;
|
||||
|
||||
wallviewVideos.each(function(index, element) {
|
||||
const row = Math.floor(index / numCols);
|
||||
const col = index % numCols;
|
||||
|
||||
$(element).css({
|
||||
left: col * itemWidth,
|
||||
top: row * itemHeight,
|
||||
width: itemWidth,
|
||||
height: itemHeight
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function openAllMonitors(){
|
||||
$.each(loadedMonitors,function(monitorId, monitor){
|
||||
var modeAccepted = monitor.mode !== 'stop' && monitor.mode !== 'idle'
|
||||
if(modeAccepted)selectMonitor(monitorId)
|
||||
})
|
||||
autoPlaceCurrentMonitorItems()
|
||||
displayInfoScreen()
|
||||
saveLayout()
|
||||
}
|
||||
|
||||
function openMonitors(monitorIds, savePlaces){
|
||||
$.each(monitorIds,function(n, monitorId){
|
||||
selectMonitor(monitorId)
|
||||
})
|
||||
autoPlaceCurrentMonitorItems()
|
||||
displayInfoScreen()
|
||||
if(savePlaces)saveLayout()
|
||||
}
|
||||
|
||||
function openNextMonitors(numberOf){
|
||||
var allLayouts = getAllLayouts()
|
||||
var ignoreMonitors = []
|
||||
var availableMonitors = []
|
||||
var numberToOpen = parseInt(numberOf) || 4;
|
||||
$.each(allLayouts,function(windowName, { layout }){
|
||||
$.each(layout,function(n, { monitorId }){
|
||||
ignoreMonitors.push(monitorId)
|
||||
})
|
||||
});
|
||||
$.each(loadedMonitors,function(monitorId, monitor){
|
||||
if(ignoreMonitors.indexOf(monitor.mid) === -1){
|
||||
var modeAccepted = monitor.mode !== 'stop' && monitor.mode !== 'idle'
|
||||
if(modeAccepted)availableMonitors.push(monitorId)
|
||||
}
|
||||
});
|
||||
for (let i = 0; i < numberToOpen; i++) {
|
||||
selectMonitor(availableMonitors[i])
|
||||
}
|
||||
autoPlaceCurrentMonitorItems()
|
||||
displayInfoScreen()
|
||||
saveLayout()
|
||||
}
|
||||
|
||||
function closeAllMonitors(){
|
||||
$.each(loadedMonitors,function(monitorId, monitor){
|
||||
deselectMonitor(monitorId)
|
||||
})
|
||||
displayInfoScreen()
|
||||
saveLayout()
|
||||
}
|
||||
|
||||
addExtender('onDashboardReady')
|
||||
|
||||
$(document).ready(function(){
|
||||
drawMonitorList().then(() => {
|
||||
loadSavedLayout()
|
||||
setTimeout(() => {
|
||||
|
@ -283,6 +347,7 @@ $(document).ready(function(){
|
|||
onWindowResize()
|
||||
saveLayout()
|
||||
})
|
||||
executeExtender('onDashboardReady')
|
||||
},500)
|
||||
})
|
||||
$('body')
|
||||
|
@ -332,4 +397,42 @@ $(document).ready(function(){
|
|||
e.preventDefault()
|
||||
closeAllMonitors()
|
||||
})
|
||||
})
|
||||
.on('click', '.wallview-toggle-monitor-list', function(e){
|
||||
e.preventDefault();
|
||||
wallViewMonitorListContainer.toggleClass('d-none')
|
||||
return false;
|
||||
})
|
||||
.on('click', '.wallview-open-monitor-group', function(e){
|
||||
e.preventDefault();
|
||||
var el = $(this)
|
||||
var tag = el.attr('tag')
|
||||
if(!tag){
|
||||
for(monitorId of Object.keys(loadedMonitors)){
|
||||
selectMonitor(monitorId)
|
||||
}
|
||||
}else{
|
||||
var tags = getListOfTagsFromMonitors()
|
||||
var monitorIds = tags[tag]
|
||||
for(monitorId of monitorIds){
|
||||
selectMonitor(monitorId)
|
||||
}
|
||||
}
|
||||
autoPlaceCurrentMonitorItems()
|
||||
displayInfoScreen()
|
||||
saveLayout()
|
||||
return false;
|
||||
})
|
||||
.on('keyup','.search-parent .search-controller',function(){
|
||||
var _this = this;
|
||||
var parent = $(this).parents('.search-parent')
|
||||
$.each(parent.find(".search-body .search-row"), function() {
|
||||
if($(this).text().toLowerCase().indexOf($(_this).val().toLowerCase()) === -1)
|
||||
$(this).hide();
|
||||
else
|
||||
$(this).show();
|
||||
});
|
||||
});
|
||||
createWebsocket(location.origin,{
|
||||
path: websocketPath
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ function createWebsocket(theURL,thePath){
|
|||
data.callbackId = callbackId
|
||||
queuedCallbacks[callbackId] = callback
|
||||
}
|
||||
console.log('Sending Data',data)
|
||||
// console.log('Sending Data',data)
|
||||
return mainSocket.emit('f',data)
|
||||
}
|
||||
mainSocket.on('ping', function(){
|
||||
|
@ -36,25 +36,12 @@ function createWebsocket(theURL,thePath){
|
|||
})
|
||||
mainSocket.on('connect',function (d){
|
||||
console.log('Connected to Websocket!')
|
||||
if(location.search === '?p2p=1'){
|
||||
mainSocket.emit('p2pInitUser',{
|
||||
user: {
|
||||
ke: $user.ke,
|
||||
mail: $user.mail,
|
||||
auth_token: $user.auth_token,
|
||||
details: $user.details,
|
||||
uid: $user.uid,
|
||||
},
|
||||
machineId: machineId
|
||||
})
|
||||
}else{
|
||||
mainSocket.f({
|
||||
f: 'init',
|
||||
ke: $user.ke,
|
||||
auth: $user.auth_token,
|
||||
uid: $user.uid
|
||||
})
|
||||
}
|
||||
mainSocket.f({
|
||||
f: 'init',
|
||||
ke: $user.ke,
|
||||
auth: $user.auth_token,
|
||||
uid: $user.uid
|
||||
})
|
||||
})
|
||||
mainSocket.on('f',function (d){
|
||||
switch(d.f){
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
$(document).ready(function(){
|
||||
var theEnclosure = $('#centralManagement')
|
||||
var theForm = theEnclosure.find('form')
|
||||
theEnclosure.find('.submit').click(function(){
|
||||
theForm.submit()
|
||||
})
|
||||
theForm.submit(function(e){
|
||||
e.preventDefault()
|
||||
var formValues = $(this).serializeObject()
|
||||
$.post(superApiPrefix + $user.sessionKey + '/mgmt/save',{
|
||||
data: JSON.stringify(formValues)
|
||||
},function(data){
|
||||
console.log(data)
|
||||
if(data.ok){
|
||||
new PNotify({
|
||||
type: 'success',
|
||||
title: lang['Settings Changed'],
|
||||
text: lang.centralManagementSaved,
|
||||
})
|
||||
}
|
||||
})
|
||||
return false
|
||||
})
|
||||
})
|
|
@ -343,6 +343,7 @@ $(document).ready(function () {
|
|||
schema: schema,
|
||||
}
|
||||
);
|
||||
window.configurationEditor = configurationEditor;
|
||||
|
||||
configurationEditor.setValue(data.config);
|
||||
|
||||
|
@ -404,5 +405,4 @@ $(document).ready(function () {
|
|||
}
|
||||
});
|
||||
|
||||
window.configurationEditor = configurationEditor;
|
||||
})
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
$(document).ready(function(){
|
||||
const loadedMounts = {}
|
||||
const theEnclosure = $('#superMountManager')
|
||||
const theSearch = $('#mountManagerListSearch')
|
||||
const theTable = $('#mountManagerListTable tbody')
|
||||
const newMountForm = $('#mountManagerNewMount')
|
||||
function getMountId(theMount){
|
||||
return `${theMount.mountPoint.split('/').join('_')}`
|
||||
}
|
||||
function drawMountToTable(theMount){
|
||||
var html = `
|
||||
<tr row-mounted="${getMountId(theMount)}">
|
||||
<td class="align-middle">
|
||||
<div>${theMount.device}</div>
|
||||
<div><small>${theMount.mountPoint}</small></div>
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
${theMount.type}
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
${theMount.options}
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<a class="btn btn-primary btn-sm cursor-pointer edit" title="${lang.Edit}"><i class="fa fa-pencil-square-o"></i></a>
|
||||
<a class="btn btn-success btn-sm cursor-pointer setVideosDir" title="${lang.videosDir}"><i class="fa fa-download"></i></a>
|
||||
<a class="btn btn-danger btn-sm cursor-pointer delete" title="${lang.Delete}"><i class="fa fa-trash-o"></i></a>
|
||||
</td>
|
||||
</tr>`
|
||||
theTable.append(html)
|
||||
}
|
||||
function drawMountsTable(data){
|
||||
theTable.empty()
|
||||
$.each(data,function(n,theMount){
|
||||
drawMountToTable(theMount)
|
||||
});
|
||||
}
|
||||
function filterMountsTable(theSearch = '') {
|
||||
var searchQuery = theSearch.trim().toLowerCase();
|
||||
if(searchQuery === ''){
|
||||
theTable.find(`[row-mounted]`).show()
|
||||
return;
|
||||
}
|
||||
var rows = Object.values(loadedMounts);
|
||||
var filtered = []
|
||||
rows.forEach((row) => {
|
||||
var searchInString = JSON.stringify(row).toLowerCase();
|
||||
var theElement = theTable.find(`[row-mounted="${getMountId(row)}"]`)
|
||||
if(searchInString.indexOf(searchQuery) > -1){
|
||||
theElement.show()
|
||||
}else{
|
||||
theElement.hide()
|
||||
}
|
||||
})
|
||||
return filtered
|
||||
}
|
||||
function loadMounts(callback) {
|
||||
return new Promise((resolve,reject) => {
|
||||
$.getJSON(superApiPrefix + $user.sessionKey + '/mountManager/list',function(data){
|
||||
$.each(data.mounts,function(n,theMount){
|
||||
loadedMounts[getMountId(theMount)] = theMount;
|
||||
})
|
||||
drawMountsTable(data.mounts)
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
function addMount(form) {
|
||||
return new Promise((resolve,reject) => {
|
||||
// const { sourceTarget, localPath, mountType, options } = form;
|
||||
$.post(superApiPrefix + $user.sessionKey + '/mountManager/mount', form,function(data){
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
function removeMount(localPath) {
|
||||
return new Promise((resolve,reject) => {
|
||||
$.post(superApiPrefix + $user.sessionKey + '/mountManager/removeMount',{
|
||||
localPath
|
||||
},function(data){
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
function setVideosDir(localPath, pathInside) {
|
||||
return new Promise((resolve,reject) => {
|
||||
$.post(superApiPrefix + $user.sessionKey + '/mountManager/setVideosDir',{
|
||||
localPath,
|
||||
pathInside
|
||||
},function(data){
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
function launchSetVideoDirConfirm(localPath){
|
||||
$.confirm.create({
|
||||
title: lang['Set New Videos Directory'],
|
||||
body: `<b>${lang['Mount Path']} : ${localPath}</b><br>${lang.setVideosDirWarning} ${lang.restartRequired}<br><br><input placeholder="${lang['Path Inside Mount']}" class="form-control" id="newVideosDirInnerPath">`,
|
||||
clickOptions: {
|
||||
class: 'btn-success',
|
||||
title: lang.Save,
|
||||
},
|
||||
clickCallback: async function(){
|
||||
const pathInside = $('#newVideosDirInnerPath').val().trim();
|
||||
const response = await setVideosDir(localPath, pathInside);
|
||||
if(response.ok){
|
||||
new PNotify({
|
||||
title: lang['New Videos Directory Set'],
|
||||
text: lang.restartRequired,
|
||||
type: 'success'
|
||||
})
|
||||
}else{
|
||||
new PNotify({
|
||||
title: lang['Action Failed'],
|
||||
text: lang['See System Logs'],
|
||||
type: 'danger'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
newMountForm.submit(async function(e){
|
||||
e.preventDefault();
|
||||
const form = newMountForm.serializeObject();
|
||||
$.each(form, function(key,val){form[key] = val.trim()});
|
||||
const response = await addMount(form);
|
||||
const notify = {
|
||||
title: lang['Mount Added'],
|
||||
text: lang.mountAddedText,
|
||||
type: 'success'
|
||||
}
|
||||
if(!response.ok){
|
||||
notify.title = lang['Failed to Add Mount']
|
||||
notify.text = response.error
|
||||
notify.type = 'danger'
|
||||
}else{
|
||||
const theMount = response.mount
|
||||
const mountId = getMountId(theMount);
|
||||
theTable.find(`[row-mounted="${mountId}"]`).remove()
|
||||
loadedMounts[mountId] = theMount;
|
||||
drawMountToTable(theMount);
|
||||
}
|
||||
new PNotify(notify)
|
||||
return false;
|
||||
});
|
||||
theTable.on('click','.delete', async function(e){
|
||||
const el = $(this).parents('[row-mounted]')
|
||||
const mountId = el.attr('row-mounted');
|
||||
const theMount = loadedMounts[mountId]
|
||||
const localPath = theMount.mountPoint
|
||||
$.confirm.create({
|
||||
title: lang['Delete Mount'],
|
||||
body: `<b>${lang['Mount Path']} : ${localPath} (${theMount.type})</b><br><small>${theMount.device}</small><br>${lang.setVideosDirWarning}`,
|
||||
clickOptions: {
|
||||
class: 'btn-danger',
|
||||
title: lang.Delete,
|
||||
},
|
||||
clickCallback: async function(){
|
||||
const response = await removeMount(localPath);
|
||||
if(response.ok){
|
||||
el.remove()
|
||||
}else{
|
||||
new PNotify({
|
||||
title: lang['Failed to Remove Mount'],
|
||||
text: lang['See System Logs'],
|
||||
type: 'danger'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
theTable.on('click','.edit', async function(e){
|
||||
const el = $(this).parents('[row-mounted]')
|
||||
const mountId = el.attr('row-mounted');
|
||||
const theMount = loadedMounts[mountId]
|
||||
newMountForm.find('[name="sourceTarget"]').val(theMount.device)
|
||||
newMountForm.find('[name="localPath"]').val(theMount.mountPoint)
|
||||
newMountForm.find('[name="mountType"]').val(theMount.type)
|
||||
newMountForm.find('[name="options"]').val(theMount.options)
|
||||
})
|
||||
theTable.on('click','.setVideosDir', function(e){
|
||||
const el = $(this).parents('[row-mounted]')
|
||||
const mountId = el.attr('row-mounted');
|
||||
const theMount = loadedMounts[mountId]
|
||||
const localPath = theMount.mountPoint
|
||||
launchSetVideoDirConfirm(localPath)
|
||||
})
|
||||
theEnclosure.on('click','.setDefaultVideosDir', function(e){
|
||||
launchSetVideoDirConfirm('__DIR__/videos')
|
||||
})
|
||||
theSearch.keydown(function(){
|
||||
const value = $(this).val().trim()
|
||||
filterMountsTable(value)
|
||||
})
|
||||
onInitSuccess(loadMounts)
|
||||
})
|
|
@ -39,6 +39,7 @@
|
|||
<script src="<%-window.libURL%>assets/js/bs5.schedules.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.liveGrid.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.liveGrid.cycle.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.liveGrid.gamepad.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.liveGrid.keyboard.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.regionEditor.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.timelapseViewer.js"></script>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title><%-pageTitle%></title>
|
||||
<%- include('header-title'); %>
|
||||
<meta charset="utf-8">
|
||||
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, shrink-to-fit=no' name='viewport' />
|
||||
<meta name="description" content="Shinobi">
|
||||
|
@ -70,7 +70,7 @@
|
|||
%>
|
||||
<script>
|
||||
window.getAdminApiPrefix = function(piece){
|
||||
return "<%=originalURL%><%= config.webPaths.adminApiPrefix %><%- $user.auth_token %>/"
|
||||
return "<%= config.webPaths.adminApiPrefix %><%- $user.auth_token %>/"
|
||||
}
|
||||
</script>
|
||||
<% customAutoLoad.LibsCss.forEach(function(lib){ %>
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
const showMonitors = define.SideMenu.showMonitors; %>
|
||||
<nav id="<%- menuInfo.id || 'sidebarMenu' %>" class="<%- menuInfo.class || 'col-md-3 col-lg-2 d-md-block sidebar collapse' %>">
|
||||
<div id="menu-side" class="position-sticky">
|
||||
<div class="align-items-center d-flex flex-row my-3 m-3 btn-default rounded shadow-sm cursor-pointer" style="overflow: hidden;">
|
||||
<div class="align-items-center d-flex flex-row my-3 m-3 btn-default rounded shadow-sm cursor-pointer" style="overflow: hidden;border-bottom: 2px solid <%- config.userHasSubscribed ? '#1f80f9' : 'red' %>;">
|
||||
<div class="align-items-center mr-3 py-3 pl-3">
|
||||
<div class="lh-1 <%- showMonitors ? `toggle-menu-collapse` : '' %>">
|
||||
<div class="d-sm-none lh-1 <%- showMonitors ? `toggle-menu-collapse` : '' %>">
|
||||
<img src="<%- window.libURL + config.logoLocation196x196 %>" width="42" style="<%- config.logoLocation76x76Style %>">
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<div class="tab-pane text-left" id="centralManagement" role="tabpanel">
|
||||
<form>
|
||||
<div class="row" style="display:flex">
|
||||
<div class="col-md-12">
|
||||
<div class="card bg-dark text-white mb-4">
|
||||
<div class="card-header">
|
||||
<%- lang['Central Management'] %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<%
|
||||
if(!config.enableMgmtConnect || !config.userHasSubscribed){ %>
|
||||
<div class="text-center"><%- lang.centralManagementNotEnabled %></div>
|
||||
<% }else{ %>
|
||||
<div class="form-group">
|
||||
<input placeholder="<%-lang['Server URL']%>" class="form-control btn btn-dark text-left text-white" type="text" name="managementServer" value="<%- config.managementServer %>" placeholder="ws://127.0.0.1:8663">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input placeholder="<%-lang['P2P API Key']%>" class="form-control btn btn-dark text-left text-white" type="text" name="peerConnectKey" value="<%- config.peerConnectKey %>" placeholder="API Key from https://licenses.shinobi.video">
|
||||
</div>
|
||||
<div class="btn-group d-flex flex-row">
|
||||
<a href="#" class="submit flex-grow-1 btn btn-success"><i class="fa fa-check"></i> <%- lang.Save %></a>
|
||||
</div>
|
||||
<% }
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script src="<%-window.libURL%>assets/js/super.centralManagement.js" type="text/javascript"></script>
|
||||
</div>
|
|
@ -0,0 +1,47 @@
|
|||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form class="card bg-dark grey mt-1" id="mountManagerNewMount">
|
||||
<div class="card-header">
|
||||
<%- lang['Add Mount'] %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="form-group">
|
||||
<label><%- lang.Source %></label>
|
||||
<input type="text" placeholder="//192.168.1.200/shared/folder" class="form-control" name="sourceTarget" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><%- lang['Mount Path'] %></label>
|
||||
<input type="text" placeholder="/mnt/newmount" class="form-control" name="localPath" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><%- lang['Mount Type'] %></label>
|
||||
<select type="text" class="form-control" name="mountType">
|
||||
<option value="cifs" selected>CIFS</option>
|
||||
<option value="nfs">NFS</option>
|
||||
<option value="ext4">ext4</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><%- lang.Options %></label>
|
||||
<input type="text" placeholder="username=username,password=password,rw,iocharset=utf8,file_mode=0777,dir_mode=0777 0 0" value="username=username,password=password,rw,iocharset=utf8,file_mode=0777,dir_mode=0777 0 0" class="form-control" name="options" />
|
||||
</div>
|
||||
<div><button type="submit" class="btn btn-round btn-block btn-default mb-0"><i class="fa fa-check"></i> <%- lang.Save %></button></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-4 pb-0 m-0">
|
||||
<div id="mountManagerList">
|
||||
<div class="form-group">
|
||||
<a class="btn btn-block btn-info setDefaultVideosDir"><%- lang['Use Default Videos Directory'] %></a>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input id="mountManagerListSearch" type="text" placeholder="<%- lang.Search %>" class="form-control" />
|
||||
</div>
|
||||
<table class="table table-striped" id="mountManagerListTable">
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<link rel="stylesheet" href="<%-window.libURL%>assets/css/super.mountManager.css">
|
||||
<script src="<%-window.libURL%>assets/js/super.mountManager.js" type="text/javascript"></script>
|
|
@ -1,5 +1,4 @@
|
|||
<div class="row">
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="card bg-dark grey mt-1">
|
||||
<div class="card-header">
|
||||
|
|
|
@ -18,7 +18,10 @@
|
|||
var hasGUI = getAddon('gui')
|
||||
var isFullscreen = getAddon('fullscreen')
|
||||
var isRelativeUrl = getAddon('relative')
|
||||
if(forceUrlPrefix){
|
||||
var hasHost = getAddon('host')
|
||||
if(hasHost){
|
||||
urlPrefix = decodeURI(hasHost)
|
||||
}else if(forceUrlPrefix){
|
||||
urlPrefix = forceUrlPrefix
|
||||
}else if(isRelativeUrl){
|
||||
urlPrefix = ''
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
<% var pageTitle = lang.Shinobi %>
|
||||
|
||||
<%- include('blocks/header', {pageTitle: pageTitle}); %>
|
||||
<%- include('blocks/header'); %>
|
||||
<%- include('blocks/header-loggedIn'); %>
|
||||
<!-- Page -->
|
||||
<%- include('blocks/home'); %>
|
||||
|
|
|
@ -89,6 +89,17 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" data-bs-target="#superPluginManager" role="tab"><%-lang['Plugin Manager']%></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" data-bs-target="#superMountManager" role="tab"><%-lang['Mount Manager']%></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" data-bs-target="#centralManagement" role="tab"><%-lang['Central Management']%></a>
|
||||
</li>
|
||||
<% customAutoLoad.superPageTabs.forEach(function(block){ %>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-bs-toggle="tab" data-bs-target="#<%- block.target %>" role="tab"><%- block.label %></a>
|
||||
</li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
<div class="card-body text-white" style="background:#263343">
|
||||
<!-- Tab panes -->
|
||||
|
@ -125,6 +136,10 @@
|
|||
<div class="tab-pane text-left" id="superPluginManager" role="tabpanel">
|
||||
<%- include('blocks/superPluginManager'); %>
|
||||
</div>
|
||||
<div class="tab-pane text-left" id="superMountManager" role="tabpanel">
|
||||
<%- include('blocks/superMountManager'); %>
|
||||
</div>
|
||||
<%- include('blocks/superCentralManagement'); %>
|
||||
<% customAutoLoad.superPageBlocks.forEach(function(block){ %>
|
||||
<%- include(block) %>
|
||||
<% }) %>
|
||||
|
@ -220,7 +235,10 @@ switch($user.lang){
|
|||
$.ccio.ws = io(`${location.origin.split('/super')[0]}/`,{
|
||||
path : tool.checkCorrectPathEnding(location.pathname)+'socket.io'
|
||||
});
|
||||
|
||||
const onInitSuccessExtensions = [];
|
||||
function onInitSuccess(theAction){
|
||||
onInitSuccessExtensions.push(theAction)
|
||||
}
|
||||
$.ccio.cx=function(x){return $.ccio.ws.emit('super',x)}
|
||||
$.ccio.ws.on('connect',function(d){
|
||||
$.ccio.cx({f:'init',mail:$user.mail,pass:$user.pass,machineId: `<%- config.machineId %>`})
|
||||
|
@ -232,6 +250,9 @@ $.ccio.ws.on('f',function(d){
|
|||
drawUserList()
|
||||
drawSystemLogs()
|
||||
drawSystemInfo()
|
||||
onInitSuccessExtensions.forEach((theAction) => {
|
||||
theAction()
|
||||
})
|
||||
break;
|
||||
case'log':
|
||||
$.ccio.tm(4,d.log,'#logs-list')
|
||||
|
|
|
@ -36,10 +36,11 @@
|
|||
var originalURL = `${urlPrefix}`
|
||||
%>
|
||||
<%- include('blocks/header-favicon') %>
|
||||
<script>window.lang=<%- JSON.stringify(lang) %>;</script>
|
||||
<script>window.$user=<%- JSON.stringify($user) %>;</script>
|
||||
<script>window.urlPrefix = "<%- urlPrefix || '' %>";</script>
|
||||
<script>window.groupKey = "<%- groupKey || '' %>";</script>
|
||||
<script>window.authKey = "<%- authKey || '' %>";</script>
|
||||
<script>window.groupKey = "<%- groupKey || '' %>";$user.ke = groupKey;</script>
|
||||
<script>window.authKey = "<%- authKey || '' %>";$user.auth_token = authKey;</script>
|
||||
<script>window.userHasSubscribed = <%- config.userHasSubscribed ? 'true' : 'false' %>;</script>
|
||||
<script src="<%- urlPrefix %>assets/vendor/js/socket.io.min.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/vendor/js/jquery.min.js"></script>
|
||||
|
@ -59,6 +60,9 @@
|
|||
|
||||
</div>
|
||||
<div id="wallview-controls" class="text-end">
|
||||
<a class="btn btn-primary wallview-cycle-control wallview-cycle-back" style="display:none" href="#"><i class="fa fa-arrow-left"></i></a>
|
||||
<a class="btn btn-primary wallview-cycle" href="#"><%- lang['Cycle'] %></a>
|
||||
<a class="btn btn-primary wallview-cycle-control wallview-cycle-front" style="display:none" href="#"><i class="fa fa-arrow-right"></i></a>
|
||||
<a class="btn btn-primary wallview-open-all" href="#"><%- lang['Open All'] %></a>
|
||||
<a class="btn btn-danger wallview-close-all" href="#"><%- lang['Close All'] %></a>
|
||||
<a class="btn btn-success wallview-autoplace" href="#"><%- lang['Auto Placement'] %></a>
|
||||
|
@ -73,16 +77,27 @@
|
|||
<% }) %>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="dropdown d-inline-block">
|
||||
<a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="monitorListButton" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<%- lang.Monitors %>
|
||||
</a>
|
||||
<ul id="wallview-monitorList" class="dropdown-menu" aria-labelledby="monitorListButton"></ul>
|
||||
<a class="btn btn-secondary wallview-toggle-monitor-list" href="#"><%- lang.Monitors %></a>
|
||||
</div>
|
||||
<div id="wallview-monitorList-container" class="d-none search-parent">
|
||||
<div class="p-2">
|
||||
<a class="btn btn-success d-block wallview-autoplace" href="#"><%- lang['Auto Placement'] %></a>
|
||||
</div>
|
||||
<div class="px-2 pb-2">
|
||||
<input class="form-control search-controller" placeholder="<%- lang.Search %>">
|
||||
</div>
|
||||
<div id="wallview-monitorList" class="px-2 pb-2 search-body">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <script src="<%- urlPrefix %>assets/js/bs5.embed.utils.js"></script> -->
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.embed.utils.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/vendor/bootstrap5/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/vendor/js/pnotify.custom.min.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/vendor/js/jquery-ui.min.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.extenders.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.websocket.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.wallview.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.wallview.gamepad.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.wallview.cycle.js"></script>
|
||||
|
|
Loading…
Reference in New Issue