Merge branch 'dev' into 'master'
Fragrant Foliage See merge request Shinobi-Systems/Shinobi!503alpha
commit
947de85a10
239
.gitlab-ci.yml
239
.gitlab-ci.yml
|
@ -1,239 +0,0 @@
|
|||
docker-latest-build:
|
||||
image: docker:latest
|
||||
stage: build
|
||||
variables:
|
||||
EXCLUDE_DB: "false"
|
||||
BASE_IMAGE: "node:18-buster-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)"
|
||||
NO_DB_SUFIX="$(${EXCLUDE_DB} && echo '-no-db' || echo '')"
|
||||
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 EXCLUDE_DB="${EXCLUDE_DB}" --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-latest-no-db-build:
|
||||
image: docker:latest
|
||||
stage: build
|
||||
variables:
|
||||
EXCLUDE_DB: "true"
|
||||
BASE_IMAGE: "node:18-buster-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)"
|
||||
NO_DB_SUFIX="$(${EXCLUDE_DB} && echo '-no-db' || echo '')"
|
||||
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 EXCLUDE_DB="${EXCLUDE_DB}" --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-arm32v7-build:
|
||||
image: docker:latest
|
||||
stage: build
|
||||
variables:
|
||||
EXCLUDE_DB: "false"
|
||||
BASE_IMAGE: "arm32v7/node:18-buster-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)"
|
||||
NO_DB_SUFIX="$(${EXCLUDE_DB} && echo '-no-db' || echo '')"
|
||||
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 EXCLUDE_DB="${EXCLUDE_DB}" --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-arm32v7-no-db-build:
|
||||
image: docker:latest
|
||||
stage: build
|
||||
variables:
|
||||
EXCLUDE_DB: "true"
|
||||
BASE_IMAGE: "arm32v7/node:18-buster-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)"
|
||||
NO_DB_SUFIX="$(${EXCLUDE_DB} && echo '-no-db' || echo '')"
|
||||
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 EXCLUDE_DB="${EXCLUDE_DB}" --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-arm64v8-build:
|
||||
image: docker:latest
|
||||
stage: build
|
||||
variables:
|
||||
EXCLUDE_DB: "false"
|
||||
BASE_IMAGE: "arm64v8/node:18-buster-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)"
|
||||
NO_DB_SUFIX="$(${EXCLUDE_DB} && echo '-no-db' || echo '')"
|
||||
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 EXCLUDE_DB="${EXCLUDE_DB}" --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-arm64v8-no-db-build:
|
||||
image: docker:latest
|
||||
stage: build
|
||||
variables:
|
||||
EXCLUDE_DB: "true"
|
||||
BASE_IMAGE: "arm64v8/node:18-buster-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)"
|
||||
NO_DB_SUFIX="$(${EXCLUDE_DB} && echo '-no-db' || echo '')"
|
||||
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 EXCLUDE_DB="${EXCLUDE_DB}" --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:
|
||||
EXCLUDE_DB: "false"
|
||||
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)"
|
||||
NO_DB_SUFIX="$(${EXCLUDE_DB} && echo '-no-db' || echo '')"
|
||||
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 EXCLUDE_DB="${EXCLUDE_DB}" --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-no-db-build:
|
||||
image: docker:latest
|
||||
stage: build
|
||||
variables:
|
||||
EXCLUDE_DB: "true"
|
||||
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)"
|
||||
NO_DB_SUFIX="$(${EXCLUDE_DB} && echo '-no-db' || echo '')"
|
||||
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 EXCLUDE_DB="${EXCLUDE_DB}" --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,3 +1,9 @@
|
|||
⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
|
||||
# OUTDATED, Newest Docker method here :
|
||||
https://gitlab.com/Shinobi-Systems/ShinobiDocker
|
||||
⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
|
||||
|
||||
|
||||
# Install Shinobi with Docker
|
||||
|
||||
**Warning :** It is recommended that you have a dedicated machine for Shinobi even if you intend to use Docker. If you are willing to install directly on the operating system please consider installing Ubuntu 22.04 and using the Ninja Way.
|
||||
|
|
|
@ -5750,6 +5750,10 @@ module.exports = function(s,config,lang){
|
|||
name: lang['Can Get Monitors'],
|
||||
value: 'get_monitors',
|
||||
},
|
||||
{
|
||||
name: lang['Can Edit Monitors'],
|
||||
value: 'edit_monitors',
|
||||
},
|
||||
{
|
||||
name: lang['Can Control Monitors'],
|
||||
value: 'control_monitors',
|
||||
|
@ -6606,7 +6610,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",
|
||||
},
|
||||
{
|
||||
|
@ -6618,11 +6622,13 @@ module.exports = function(s,config,lang){
|
|||
{
|
||||
"name": "user",
|
||||
"field": lang['Camera Username'],
|
||||
"description": lang.fieldTextOnvifScanCameraUsername,
|
||||
"placeholder": "Can be left blank.",
|
||||
},
|
||||
{
|
||||
"name": "pass",
|
||||
"field": lang['Camera Password'],
|
||||
"description": lang.fieldTextOnvifScanCameraPassword,
|
||||
"fieldType": "password",
|
||||
},
|
||||
{
|
||||
|
@ -6631,8 +6637,13 @@ module.exports = function(s,config,lang){
|
|||
{
|
||||
"fieldType": "btn",
|
||||
"forForm": true,
|
||||
"class": `btn-block btn-success`,
|
||||
"btnContent": `${lang['Search']}<span class="_loading" style="display:none"> <i class="fa fa-pulse fa-spinner"></i></span>`,
|
||||
"class": `btn-success start-scan`,
|
||||
"btnContent": `${lang['Search']}`,
|
||||
},
|
||||
{
|
||||
"fieldType": "btn",
|
||||
"class": `btn-danger stop-scan d-none`,
|
||||
"btnContent": `${lang['Stop']}`,
|
||||
},
|
||||
{
|
||||
"fieldType": "btn",
|
||||
|
@ -6653,18 +6664,7 @@ module.exports = function(s,config,lang){
|
|||
"class": "onvif_result row",
|
||||
}
|
||||
]
|
||||
},
|
||||
"Other Devices": {
|
||||
"name": lang['Other Devices'],
|
||||
"color": "danger",
|
||||
"section-pre-class": "col-md-12",
|
||||
"info": [
|
||||
{
|
||||
"fieldType": "div",
|
||||
"class": "onvif_result_error row",
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"Camera Probe": {
|
||||
|
@ -7696,6 +7696,11 @@ module.exports = function(s,config,lang){
|
|||
pageOpen: 'liveGrid',
|
||||
addUl: true,
|
||||
ulItems: [
|
||||
{
|
||||
label: lang['Open Wall Display'],
|
||||
class: 'open-wallview cursor-pointer',
|
||||
color: 'blue',
|
||||
},
|
||||
{
|
||||
label: lang['Open All Monitors'],
|
||||
class: 'open-all-monitors cursor-pointer',
|
||||
|
@ -9191,6 +9196,12 @@ module.exports = function(s,config,lang){
|
|||
<div class="btn-group">
|
||||
<a class="btn btn-sm btn-primary" id="timeline-date-selector" title="${lang.Date}"><i class="fa fa-calendar"></i></a>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-primary btn-sm dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa fa-video-camera"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu px-3 bg-dark" id="timeline-monitor-list"></ul>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
"Geolocation": "Geolocation",
|
||||
"fieldTextGeolocation": "The map coordinates of this camera in the real world. This will plot a point for your camera on the Monitor Map.",
|
||||
"playUntilVideoEnd": "Play until video end",
|
||||
"Monitor Saved": "Monitor Saved",
|
||||
"Auto Placement": "Auto Placement",
|
||||
"Unmute": "Unmute",
|
||||
"byUser": "by user",
|
||||
"accountDeleted": "Account Deleted",
|
||||
|
@ -135,7 +137,12 @@
|
|||
"Viewing Server Stats": "Viewing Server Stats",
|
||||
"Connected Users": "Connected Users",
|
||||
"Registered Servers": "Registered Servers",
|
||||
"Open All": "Open All",
|
||||
"Close All": "Close All",
|
||||
"Open All Monitors": "Open All Monitors",
|
||||
"Open Wall Display": "Open Wall Display",
|
||||
"New Wall Display": "New Wall Display",
|
||||
"openWallViewInfo": "Open Monitors in the top right of this window.",
|
||||
"Accounts": "Accounts",
|
||||
"Settings": "Settings",
|
||||
"Count Objects only inside Regions": "Count Objects only inside Regions",
|
||||
|
@ -298,6 +305,7 @@
|
|||
"Can Get Logs": "Can Get Logs",
|
||||
"Can Authenticate Websocket": "Can Authenticate Websocket",
|
||||
"Can Control Monitors": "Can Control Monitors",
|
||||
"Can Edit Monitors": "Can Edit Monitors",
|
||||
"Can View Snapshots": "Can View Snapshots",
|
||||
"Can View Streams": "Can View Streams",
|
||||
"Can View Videos": "Can View Videos",
|
||||
|
@ -417,6 +425,8 @@
|
|||
"Audio streams only": "Audio streams only",
|
||||
"Audio stream only from first feed": "Audio stream only from first feed",
|
||||
"sorryNothingWasFound": "Sorry, nothing was found.",
|
||||
"fieldTextOnvifScanCameraUsername": "Commas are used to separate potential usernames to attempt while scanning. If you have multiple usernames and passwords then all variations will be attempted.",
|
||||
"fieldTextOnvifScanCameraPassword": "Commas and @ are not allowed in passwords. Commas are used to separate potential passwords to attempt while scanning. Double click this field to reveal the currently set value.",
|
||||
"Event Occurred": "Event Occurred",
|
||||
"ONVIF Port": "ONVIF Port",
|
||||
"ONVIF Scanner": "ONVIF Scanner",
|
||||
|
@ -424,9 +434,10 @@
|
|||
"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",
|
||||
"ONVIFErr400": "Found ONVIF port but authorization failed when retrieving the Stream URL. Check username and password used for scan. Make sure your camera time and server time are synced.",
|
||||
"ONVIFErr400": "Bad Request. Found ONVIF port but failed when retrieving the Stream URL. Check username and password used for scan. Make sure your camera time and server time are synced.",
|
||||
"ONVIFErr405": "Method Not Allowed. Check username and password used for scan.",
|
||||
"ONVIFErr404": "Not Found. This may just be the web panel for a network device.",
|
||||
"ONVIFErr401": "Not Authorized. Found ONVIF port but authorization failed when retrieving the Stream URL. Check username and password used for scan. Make sure your camera time and server time are synced.",
|
||||
"Scan Settings": "Scan Settings",
|
||||
"ONVIFnote": "Discover ONVIF devices on networks outside your own or leave it blank to scan your current network. <br>Username and Password can be left blank.",
|
||||
"Range or Single": "Range or Single",
|
||||
|
@ -1426,6 +1437,7 @@
|
|||
"Select atleast one monitor to delete": "Select atleast one monitor to delete.",
|
||||
"Use Built-In": "Use Built-In",
|
||||
"Add Cameras": "Add Cameras",
|
||||
"addAllCamerasText": "You are about to add 9001 cameras to your system.",
|
||||
"Add Camera": "Add Camera",
|
||||
"Delete Camera": "Delete Camera",
|
||||
"Event Rules": "Event Rules",
|
||||
|
@ -1448,7 +1460,7 @@
|
|||
"Original Choice": "Original Choice",
|
||||
"Legacy Webhook": "Legacy Webhook",
|
||||
"eventFilterActionText": "These are the actions that occur from the filter conditions that have succeeded. \"Original Choice\" refers to the option you had chosen in your Monitor's Settings.",
|
||||
"eventFilterEnableNoticeText": "This is an advnaced feature. Ensure you have enabled \"Event Filters\" in your Detector Settings section. If you cannot see this option toggle Simple to Advanced in the bottom right corner of the Monitor Settings.",
|
||||
"eventFilterEnableNoticeText": "This is an advanced feature. Ensure you have enabled \"Event Filters\" in your Detector Settings section. If you cannot see this option toggle Simple to Advanced in the bottom right corner of the Monitor Settings.",
|
||||
"Telegram": "Telegram",
|
||||
"Before": "Before",
|
||||
"After": "After",
|
||||
|
|
|
@ -106,6 +106,8 @@ module.exports = function(s,config,lang){
|
|||
permissions: {}
|
||||
})
|
||||
callback(err,user,true)
|
||||
}else{
|
||||
callback(lang['Not Authorized'],null,false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -150,8 +150,8 @@ module.exports = (s,config,lang,app) => {
|
|||
alternateLoginsFieldList[alternateLoginsFieldList.length - 1].btns.push({
|
||||
"class": `btn-info ldap-sign-in`,
|
||||
"btnContent": `<i class="fa fa-group"></i> ${lang['Link LDAP Account']}`,
|
||||
})
|
||||
s.customAutoLoadTree['LibsJs'].push(`dash2.ldapSignIn.js`)
|
||||
});
|
||||
s.customAutoLoadTree['AssetsJs'].push(`bs5.ldapSignIn.js`)
|
||||
})
|
||||
/**
|
||||
* API : Add Token Window (Sign-In to LDAP) (GET)
|
||||
|
|
|
@ -179,7 +179,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
}
|
||||
s.insertDatabaseRow(monitorConfig,insert)
|
||||
s.insertCompletedVideoExtensions.forEach(function(extender){
|
||||
extender(monitorConfig,insert)
|
||||
extender(activeMonitor, monitorConfig, insert)
|
||||
})
|
||||
//purge over max
|
||||
s.purgeDiskForGroup(data.ke)
|
||||
|
|
|
@ -8,6 +8,9 @@ let config = workerData.config
|
|||
let lang = workerData.lang
|
||||
let sslInfo = config.ssl || {}
|
||||
let remoteConnectionPort = config.easyRemotePort || (sslInfo && sslInfo.port && (sslInfo.enabled !== false) ? sslInfo.port : config.port || 8080)
|
||||
const multipleSelected = config.p2pHostMultiSelected instanceof Array && config.p2pHostMultiSelected.length > 0;
|
||||
const p2pApiKey = config.p2pApiKey;
|
||||
const p2pServerList = config.p2pServerList;
|
||||
const net = require("net")
|
||||
const bson = require('bson')
|
||||
const WebSocket = require('cws')
|
||||
|
@ -28,7 +31,16 @@ const s = {
|
|||
parentPort.on('message',(data) => {
|
||||
switch(data.f){
|
||||
case'init':
|
||||
initialize()
|
||||
if(multipleSelected){
|
||||
for(aSelection of config.p2pHostMultiSelected){
|
||||
clearAllTimeouts(aSelection)
|
||||
initialize(aSelection)
|
||||
}
|
||||
}else{
|
||||
const singleSelection = config.p2pHostSelected;
|
||||
clearAllTimeouts(singleSelection)
|
||||
initialize(singleSelection)
|
||||
}
|
||||
break;
|
||||
case'exit':
|
||||
s.debugLog('Closing P2P Connection...')
|
||||
|
@ -36,11 +48,11 @@ parentPort.on('message',(data) => {
|
|||
break;
|
||||
}
|
||||
})
|
||||
var socketCheckTimer = null
|
||||
var heartbeatTimer = null
|
||||
var heartBeatCheckTimout = null
|
||||
var onClosedTimeout = null
|
||||
let stayDisconnected = false
|
||||
var socketCheckTimer = {}
|
||||
var heartbeatTimer = {}
|
||||
var heartBeatCheckTimout = {}
|
||||
var onClosedTimeout = {}
|
||||
let stayDisconnected = {}
|
||||
const requestConnections = {}
|
||||
const requestConnectionsData = {}
|
||||
function getRequestConnection(requestId){
|
||||
|
@ -48,29 +60,28 @@ function getRequestConnection(requestId){
|
|||
write: () => {}
|
||||
}
|
||||
}
|
||||
function clearAllTimeouts(){
|
||||
clearInterval(heartbeatTimer)
|
||||
clearTimeout(heartBeatCheckTimout)
|
||||
clearTimeout(onClosedTimeout)
|
||||
function clearAllTimeouts(p2pServerAddress){
|
||||
clearInterval(heartbeatTimer[p2pServerAddress])
|
||||
clearTimeout(heartBeatCheckTimout[p2pServerAddress])
|
||||
clearTimeout(onClosedTimeout[p2pServerAddress])
|
||||
}
|
||||
function startConnection(p2pServerAddress,subscriptionId){
|
||||
let tunnelToP2P
|
||||
stayDisconnected = false
|
||||
stayDisconnected[p2pServerAddress] = false
|
||||
const allMessageHandlers = []
|
||||
async function startWebsocketConnection(key,callback){
|
||||
s.debugLog(`startWebsocketConnection EXECUTE`,new Error())
|
||||
console.log('P2P : Connecting to Konekta P2P Server...')
|
||||
function createWebsocketConnection(){
|
||||
clearAllTimeouts()
|
||||
return new Promise((resolve,reject) => {
|
||||
try{
|
||||
stayDisconnected = true
|
||||
stayDisconnected[p2pServerAddress] = true
|
||||
if(tunnelToP2P)tunnelToP2P.close()
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
tunnelToP2P = new WebSocket(p2pServerAddress);
|
||||
stayDisconnected = false;
|
||||
console.log('P2P : Connecting to Konekta P2P Server :', p2pServerAddress)
|
||||
stayDisconnected[p2pServerAddress] = false;
|
||||
tunnelToP2P.on('open', function(){
|
||||
resolve(tunnelToP2P)
|
||||
})
|
||||
|
@ -81,7 +92,7 @@ function startConnection(p2pServerAddress,subscriptionId){
|
|||
})
|
||||
tunnelToP2P.on('close', () => {
|
||||
console.log(`P2P Connection Closed!`)
|
||||
clearAllTimeouts()
|
||||
clearAllTimeouts(p2pServerAddress)
|
||||
// onClosedTimeout = setTimeout(() => {
|
||||
// disconnectedConnection();
|
||||
// },5000)
|
||||
|
@ -95,8 +106,8 @@ function startConnection(p2pServerAddress,subscriptionId){
|
|||
})
|
||||
}
|
||||
|
||||
clearInterval(socketCheckTimer)
|
||||
socketCheckTimer = setInterval(() => {
|
||||
clearInterval(socketCheckTimer[p2pServerAddress])
|
||||
socketCheckTimer[p2pServerAddress] = setInterval(() => {
|
||||
// s.debugLog('Tunnel Ready State :',tunnelToP2P.readyState)
|
||||
if(tunnelToP2P.readyState !== 1){
|
||||
s.debugLog('Tunnel NOT Ready! Reconnecting...')
|
||||
|
@ -106,10 +117,10 @@ function startConnection(p2pServerAddress,subscriptionId){
|
|||
})
|
||||
}
|
||||
function disconnectedConnection(code,reason){
|
||||
s.debugLog('stayDisconnected',stayDisconnected)
|
||||
s.debugLog('stayDisconnected',stayDisconnected[p2pServerAddress])
|
||||
clearAllTimeouts()
|
||||
s.debugLog('DISCONNECTED!')
|
||||
if(stayDisconnected)return;
|
||||
if(stayDisconnected[p2pServerAddress])return;
|
||||
s.debugLog('RESTARTING!')
|
||||
setTimeout(() => {
|
||||
if(tunnelToP2P && tunnelToP2P.readyState !== 1)startWebsocketConnection()
|
||||
|
@ -122,8 +133,8 @@ function startConnection(p2pServerAddress,subscriptionId){
|
|||
subscriptionId: subscriptionId,
|
||||
restrictedTo: config.p2pRestrictedTo || [],
|
||||
})
|
||||
clearInterval(heartbeatTimer)
|
||||
heartbeatTimer = setInterval(() => {
|
||||
clearInterval(heartbeatTimer[p2pServerAddress])
|
||||
heartbeatTimer[p2pServerAddress] = setInterval(() => {
|
||||
sendDataToTunnel({
|
||||
f: 'ping',
|
||||
})
|
||||
|
@ -158,7 +169,7 @@ function startConnection(p2pServerAddress,subscriptionId){
|
|||
// remotesocket.off('close')
|
||||
// requestConnections[requestId].end()
|
||||
// }
|
||||
const responseTunnel = await getResponseTunnel(requestId)
|
||||
const responseTunnel = await getResponseTunnel(requestId, p2pServerAddress)
|
||||
let remotesocket = new net.Socket();
|
||||
remotesocket.on('ready',() => {
|
||||
remotesocket.write(initData.buffer)
|
||||
|
@ -196,8 +207,8 @@ function startConnection(p2pServerAddress,subscriptionId){
|
|||
}
|
||||
}
|
||||
function refreshHeartBeatCheck(){
|
||||
clearTimeout(heartBeatCheckTimout)
|
||||
heartBeatCheckTimout = setTimeout(() => {
|
||||
clearTimeout(heartBeatCheckTimout[p2pServerAddress])
|
||||
heartBeatCheckTimout[p2pServerAddress] = setTimeout(() => {
|
||||
startWebsocketConnection()
|
||||
},1000 * 10 * 1.5)
|
||||
}
|
||||
|
@ -304,15 +315,16 @@ function startConnection(p2pServerAddress,subscriptionId){
|
|||
onIncomingMessage('disconnect',function(data,requestId){
|
||||
console.log(`FAILED LICENSE CHECK ON P2P`)
|
||||
const retryLater = data && data.retryLater;
|
||||
stayDisconnected = !retryLater
|
||||
stayDisconnected[p2pServerAddress] = !retryLater
|
||||
if(retryLater)console.log(`Retrying P2P Later...`)
|
||||
})
|
||||
return tunnelToP2P;
|
||||
}
|
||||
const responseTunnels = {}
|
||||
async function getResponseTunnel(originalRequestId){
|
||||
return responseTunnels[originalRequestId] || await createResponseTunnel(originalRequestId)
|
||||
async function getResponseTunnel(originalRequestId, p2pServerAddress){
|
||||
return responseTunnels[originalRequestId] || await createResponseTunnel(originalRequestId, p2pServerAddress)
|
||||
}
|
||||
function createResponseTunnel(originalRequestId){
|
||||
function createResponseTunnel(originalRequestId, p2pServerAddress){
|
||||
const responseTunnelMessageHandlers = []
|
||||
function onMessage(key,callback){
|
||||
responseTunnelMessageHandlers.push({
|
||||
|
@ -321,7 +333,7 @@ function createResponseTunnel(originalRequestId){
|
|||
})
|
||||
}
|
||||
return new Promise((resolve,reject) => {
|
||||
const responseTunnel = new WebSocket(config.selectedHost);
|
||||
const responseTunnel = new WebSocket(p2pServerAddress);
|
||||
function sendToResponseTunnel(data){
|
||||
responseTunnel.send(
|
||||
bson.serialize(data)
|
||||
|
@ -374,10 +386,9 @@ function closeResponseTunnel(originalRequestId){
|
|||
s.debugLog('closeResponseTunnel',err)
|
||||
}
|
||||
}
|
||||
function initialize(){
|
||||
const selectedP2PServerId = config.p2pServerList[config.p2pHostSelected] ? config.p2pHostSelected : Object.keys(config.p2pServerList)[0]
|
||||
const p2pServerDetails = config.p2pServerList[selectedP2PServerId]
|
||||
function initialize(p2pHostSelected){
|
||||
const selectedP2PServerId = p2pServerList[p2pHostSelected] ? p2pHostSelected : Object.keys(p2pServerList)[0]
|
||||
const p2pServerDetails = p2pServerList[selectedP2PServerId]
|
||||
const selectedHost = `${p2pServerDetails.secure ? `wss` : 'ws'}://` + p2pServerDetails.host + ':' + p2pServerDetails.p2pPort
|
||||
config.selectedHost = selectedHost
|
||||
startConnection(selectedHost,config.p2pApiKey)
|
||||
startConnection(selectedHost,p2pApiKey)
|
||||
}
|
||||
|
|
|
@ -389,12 +389,14 @@ module.exports = function(s,config,lang){
|
|||
},callback)
|
||||
}
|
||||
const moveToHomePosition = (options,callback) => {
|
||||
const nonStandardOnvif = s.group[options.ke].rawMonitorConfigurations[options.id].details.onvif_non_standard === '1'
|
||||
const groupKey = options.ke
|
||||
const monitorId = options.mid || options.id
|
||||
const nonStandardOnvif = s.group[groupKey].rawMonitorConfigurations[monitorId].details.onvif_non_standard === '1'
|
||||
const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
|
||||
return s.runOnvifMethod({
|
||||
auth: {
|
||||
ke: options.ke,
|
||||
id: options.id,
|
||||
ke: groupKey,
|
||||
id: monitorId,
|
||||
service: 'ptz',
|
||||
action: 'gotoHomePosition',
|
||||
},
|
||||
|
@ -537,5 +539,6 @@ module.exports = function(s,config,lang){
|
|||
moveToPresetPosition,
|
||||
moveCameraPtzToMatrix,
|
||||
setHomePositionPreset,
|
||||
moveToHomePosition,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
break;
|
||||
default:
|
||||
s.systemLog('CRON.js MESSAGE : ',data)
|
||||
s.debugLog('CRON.js MESSAGE : ',data)
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,116 +1,106 @@
|
|||
module.exports = function(s,config,lang){
|
||||
function hasOnvifEventsEnabled(monitorConfig) {
|
||||
return monitorConfig.details.is_onvif === '1' && monitorConfig.details.onvif_events === '1';
|
||||
}
|
||||
|
||||
module.exports = function (s, config, lang) {
|
||||
const {Cam} = require("onvif");
|
||||
const {
|
||||
triggerEvent,
|
||||
} = require('./utils.js')(s,config,lang)
|
||||
const onvifEvents = require("node-onvif-events");
|
||||
const onvifEventIds = []
|
||||
const onvifEventControllers = {}
|
||||
const startMotion = async (onvifId,monitorConfig) => {
|
||||
const groupKey = monitorConfig.ke
|
||||
const monitorId = monitorConfig.mid
|
||||
const onvifIdKey = `${monitorConfig.mid}${monitorConfig.ke}`
|
||||
const controlBaseUrl = monitorConfig.details.control_base_url || s.buildMonitorUrl(monitorConfig, true)
|
||||
const controlURLOptions = s.cameraControlOptionsFromUrl(controlBaseUrl,monitorConfig)
|
||||
const onvifPort = parseInt(monitorConfig.details.onvif_port) || 8000
|
||||
let options = {
|
||||
id: onvifId,
|
||||
hostname: controlURLOptions.host,
|
||||
username: controlURLOptions.username,
|
||||
password: controlURLOptions.password,
|
||||
port: onvifPort,
|
||||
};
|
||||
const detector = onvifEventControllers[onvifIdKey] || await onvifEvents.MotionDetector.create(options.id, options);
|
||||
function onvifEventLog(type,data){
|
||||
s.userLog({
|
||||
ke: groupKey,
|
||||
mid: monitorId
|
||||
},{
|
||||
type: type,
|
||||
msg: data
|
||||
})
|
||||
}
|
||||
onvifEventLog(`ONVIF Event Detection Listening!`)
|
||||
try {
|
||||
detector.listen((motion) => {
|
||||
if (motion) {
|
||||
onvifEventLog(`ONVIF Event Detected!`)
|
||||
triggerEvent({
|
||||
f: 'trigger',
|
||||
id: monitorId,
|
||||
ke: groupKey,
|
||||
details:{
|
||||
plug: 'onvifEvent',
|
||||
name: 'onvifEvent',
|
||||
reason: 'motion',
|
||||
confidence: 100,
|
||||
// reason: 'object',
|
||||
// matrices: [matrix],
|
||||
// imgHeight: img.height,
|
||||
// imgWidth: img.width,
|
||||
}
|
||||
})
|
||||
} else {
|
||||
onvifEventLog(`ONVIF Event Stopped`)
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
console.error(e)
|
||||
onvifEventLog(`ONVIF Event Error`,e)
|
||||
}
|
||||
return detector
|
||||
}
|
||||
async function initializeOnvifEvents(monitorConfig){
|
||||
const monitorMode = monitorConfig.mode
|
||||
const groupKey = monitorConfig.ke
|
||||
const monitorId = monitorConfig.mid
|
||||
const hasOnvifEventsEnabled = monitorConfig.details.is_onvif === '1' && monitorConfig.details.onvif_events === '1';
|
||||
if(hasOnvifEventsEnabled){
|
||||
const onvifIdKey = `${monitorConfig.mid}${monitorConfig.ke}`
|
||||
let onvifId = onvifEventIds.indexOf(onvifIdKey)
|
||||
if(onvifEventIds.indexOf(onvifIdKey) === -1){
|
||||
onvifId = onvifEventIds.length;
|
||||
onvifEventIds.push(onvifIdKey);
|
||||
}
|
||||
try{
|
||||
onvifEventControllers[onvifIdKey].close()
|
||||
s.debugLog('ONVIF Event Module Warning : This could cause a memory leak?')
|
||||
}catch(err){
|
||||
s.debugLog('ONVIF Event Module Error', err.stack);
|
||||
}
|
||||
try{
|
||||
delete(onvifEventControllers[onvifIdKey])
|
||||
s.debugLog('Can ',monitorConfig.name, 'read ONVIF Events?',monitorMode !== 'stop')
|
||||
if(monitorMode !== 'stop'){
|
||||
s.debugLog('Starting ONVIF Event Reader on ',monitorConfig.name)
|
||||
const detector = await startMotion(onvifId,monitorConfig)
|
||||
onvifEventControllers[onvifIdKey] = detector;
|
||||
}
|
||||
}catch(err){
|
||||
console.error(err)
|
||||
s.debugLog('ONVIF Event Module Start Error', err.stack);
|
||||
}
|
||||
} = require('./utils.js')(s, config, lang)
|
||||
|
||||
function handleEvent(event, monitorConfig, onvifEventLog) {
|
||||
const eventValue = event.message?.message?.data?.simpleItem?.$?.Value;
|
||||
if (eventValue === false) {
|
||||
onvifEventLog(`ONVIF Event Stopped`, `topic ${event.topic?._}`)
|
||||
return
|
||||
}
|
||||
onvifEventLog(`ONVIF Event Detected!`, `topic ${event.topic?._}`)
|
||||
triggerEvent({
|
||||
f: 'trigger',
|
||||
id: monitorConfig.mid,
|
||||
ke: monitorConfig.ke,
|
||||
details: {
|
||||
plug: 'onvifEvent',
|
||||
name: 'onvifEvent',
|
||||
reason: event.topic?._,
|
||||
confidence: 100,
|
||||
[event.message?.message?.data?.simpleItem?.$?.Name]: eventValue
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function configureOnvif(monitorConfig, onvifEventLog) {
|
||||
const controlBaseUrl = monitorConfig.details.control_base_url || s.buildMonitorUrl(monitorConfig, true)
|
||||
const controlURLOptions = s.cameraControlOptionsFromUrl(controlBaseUrl, monitorConfig)
|
||||
const onvifPort = parseInt(monitorConfig.details.onvif_port) || 8000
|
||||
|
||||
const options = {
|
||||
hostname: controlURLOptions.host,
|
||||
username: controlURLOptions.username,
|
||||
password: controlURLOptions.password,
|
||||
port: onvifPort,
|
||||
};
|
||||
|
||||
return new Cam(options, function (error) {
|
||||
if (error) {
|
||||
onvifEventLog(`ONVIF Event Error`,e)
|
||||
return
|
||||
}
|
||||
this.on('event', function (event) {
|
||||
handleEvent(event, monitorConfig, onvifEventLog);
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
const cams = {};
|
||||
|
||||
function initializeOnvifEvents(monitorConfig) {
|
||||
monitorConfig.key = `${monitorConfig.mid}${monitorConfig.ke}`
|
||||
|
||||
const onvifEventLog = function onvifEventLog(type, data) {
|
||||
s.userLog({
|
||||
ke: monitorConfig.key,
|
||||
mid: monitorConfig.mid
|
||||
}, {
|
||||
type: type,
|
||||
msg: data
|
||||
})
|
||||
}
|
||||
|
||||
if (!hasOnvifEventsEnabled(monitorConfig)) {
|
||||
cams[monitorConfig.key]?.removeAllListeners('event')
|
||||
return
|
||||
}
|
||||
if (cams[monitorConfig.key]) {
|
||||
onvifEventLog("ONVIF already listening to events")
|
||||
return;
|
||||
}
|
||||
|
||||
cams[monitorConfig.key] = configureOnvif(monitorConfig,onvifEventLog);
|
||||
}
|
||||
|
||||
s.onMonitorStart((monitorConfig) => {
|
||||
initializeOnvifEvents(monitorConfig)
|
||||
})
|
||||
|
||||
const connectionInfoArray = s.definitions["Monitor Settings"].blocks["Detector"].info
|
||||
connectionInfoArray.splice(2, 0, {
|
||||
"name": "detail=onvif_events",
|
||||
"field": lang['ONVIF Events'],
|
||||
"default": "0",
|
||||
"form-group-class": "h_onvif_input h_onvif_1",
|
||||
"form-group-class-pre-layer": "h_det_input h_det_1",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
"name": "detail=onvif_events",
|
||||
"field": lang['ONVIF Events'],
|
||||
"default": "0",
|
||||
"form-group-class": "h_onvif_input h_onvif_1",
|
||||
"form-group-class-pre-layer": "h_det_input h_det_1",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
|
|
@ -83,6 +83,7 @@ module.exports = (s,config,lang) => {
|
|||
&& detailString.matrices[0]
|
||||
&& detailString.matrices[0].tag;
|
||||
newString = newString
|
||||
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
|
||||
.replace(/{{TIME}}/g,d.currentTimestamp)
|
||||
.replace(/{{REGION_NAME}}/g,d.details.name)
|
||||
.replace(/{{SNAP_PATH}}/g,s.dir.streams+d.ke+'/'+d.id+'/s.jpg')
|
||||
|
|
|
@ -428,7 +428,7 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
if(!videoCodecisCopy || outputRequiresEncoding){
|
||||
if(videoWidth && videoHeight)streamFlags.push(`-s ${videoWidth}x${videoHeight}`)
|
||||
if(videoFps && streamType === 'mjpeg' || streamType === 'b64'){
|
||||
if(videoFps && streamType === 'mjpeg' || streamType === 'b64' || videoFps && !videoCodecisCopy){
|
||||
streamFilters.push(`fps=${videoFps}`)
|
||||
}
|
||||
}
|
||||
|
@ -495,7 +495,7 @@ module.exports = (s,config,lang) => {
|
|||
const isCudaEnabled = hasCudaEnabled(e)
|
||||
const videoFlags = []
|
||||
const videoFilters = []
|
||||
const inputMap = buildInputMap(e,e.details.input_map_choices.stream)
|
||||
const inputMap = buildInputMap(e,e.details.input_map_choices.snap)
|
||||
const { videoWidth, videoHeight } = validateDimensions(e.details.snap_scale_x,e.details.snap_scale_y)
|
||||
if(inputMap)videoFlags.push(inputMap)
|
||||
if(e.details.snap_vf)videoFilters.push(e.details.snap_vf)
|
||||
|
|
|
@ -196,7 +196,7 @@ module.exports = function(s,config,lang){
|
|||
var iconImageFile = streamDir + 'icon.jpg'
|
||||
const snapRawFilters = monitor.details.cust_snap_raw
|
||||
if(snapRawFilters)outputOptions.push(snapRawFilters);
|
||||
var ffmpegCmd = splitForFFMPEG(`-y -loglevel warning ${isDetectorStream ? '-live_start_index 2' : ''} -re ${inputOptions.join(' ')} -stimeout 4000000 -i "${url}" ${outputOptions.join(' ')} -f image2 -an -frames:v 1 "${temporaryImageFile}"`)
|
||||
var ffmpegCmd = splitForFFMPEG(`-y -loglevel warning ${isDetectorStream ? '-live_start_index 2' : ''} -re ${inputOptions.join(' ')} -timeout 4000000 -i "${url}" ${outputOptions.join(' ')} -f image2 -an -frames:v 1 "${temporaryImageFile}"`)
|
||||
try{
|
||||
await fs.promises.mkdir(streamDir, {recursive: true}, (err) => {s.debugLog(err)})
|
||||
}catch(err){
|
||||
|
@ -796,6 +796,7 @@ module.exports = function(s,config,lang){
|
|||
[
|
||||
'auth_socket',
|
||||
'get_monitors',
|
||||
'edit_monitors',
|
||||
'control_monitors',
|
||||
'get_logs',
|
||||
'watch_stream',
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
const fs = require('fs');
|
||||
const URL = require('url');
|
||||
const events = require('events');
|
||||
const Mp4Frag = require('mp4frag');
|
||||
const treekill = require('tree-kill');
|
||||
const exec = require('child_process').exec;
|
||||
const spawn = require('child_process').spawn;
|
||||
const connectionTester = require('connection-tester')
|
||||
const SoundDetection = require('shinobi-sound-detection')
|
||||
const streamViewerCountTimeouts = {}
|
||||
const { createQueueAwaited } = require('../common.js')
|
||||
module.exports = (s,config,lang) => {
|
||||
const fs = require('fs');
|
||||
const URL = require('url');
|
||||
const events = require('events');
|
||||
const Mp4Frag = require('mp4frag');
|
||||
const treekill = require('tree-kill');
|
||||
const exec = require('child_process').exec;
|
||||
const spawn = require('child_process').spawn;
|
||||
const connectionTester = require('connection-tester')
|
||||
const SoundDetection = require('shinobi-sound-detection')
|
||||
const streamViewerCountTimeouts = {}
|
||||
const { createQueueAwaited } = require('../common.js')
|
||||
const {
|
||||
applyPartialToConfiguration,
|
||||
getWarningChangesForMonitor,
|
||||
|
@ -29,6 +29,7 @@ module.exports = (s,config,lang) => {
|
|||
} = require('../events/utils.js')(s,config,lang)
|
||||
const {
|
||||
setHomePositionPreset,
|
||||
moveToHomePosition,
|
||||
} = require('../control/ptz.js')(s,config,lang)
|
||||
const {
|
||||
scanForOrphanedVideos,
|
||||
|
@ -498,7 +499,20 @@ module.exports = (s,config,lang) => {
|
|||
const groupKey = e.ke
|
||||
const monitorId = e.mid || e.id
|
||||
const activeMonitor = getActiveMonitor(groupKey,monitorId);
|
||||
const monitorConfig = copyMonitorConfiguration(groupKey,monitorId);
|
||||
const streamType = monitorConfig.details.stream_type;
|
||||
const analyzeDuration = (parseInt(monitorConfig.details.aduration) / 1000) || 10000;
|
||||
let initialHeartBeat = null
|
||||
if(streamType !== 'useSubstream'){
|
||||
initialHeartBeat = setTimeout(() => {
|
||||
resetStreamCheck({
|
||||
ke: groupKey,
|
||||
mid: monitorId,
|
||||
})
|
||||
}, analyzeDuration);
|
||||
}
|
||||
activeMonitor.spawn_exit = async function(){
|
||||
clearTimeout(initialHeartBeat)
|
||||
if(activeMonitor.isStarted === true){
|
||||
if(e.details.loglevel !== 'quiet'){
|
||||
s.userLog(e,{type:lang['Process Unexpected Exit'],msg:{msg:lang.unexpectedExitText,cmd:activeMonitor.ffmpeg}});
|
||||
|
@ -508,7 +522,6 @@ module.exports = (s,config,lang) => {
|
|||
forceCheck: true,
|
||||
checkMax: 2
|
||||
})
|
||||
const monitorConfig = copyMonitorConfiguration(groupKey,monitorId);
|
||||
s.onMonitorUnexpectedExitExtensions.forEach(function(extender){
|
||||
extender(monitorConfig,e)
|
||||
})
|
||||
|
@ -1707,7 +1720,8 @@ module.exports = (s,config,lang) => {
|
|||
activeMonitor.errorFatalCount = 0;
|
||||
delete(activeMonitor.childNode)
|
||||
if(e.details.detector_ptz_follow === '1'){
|
||||
setHomePositionPreset(e)
|
||||
// setHomePositionPreset(e)
|
||||
moveToHomePosition(e)
|
||||
}
|
||||
try{
|
||||
await launchMonitorProcesses(e)
|
||||
|
@ -1778,6 +1792,36 @@ module.exports = (s,config,lang) => {
|
|||
const streamDir = s.dir.streams + options.ke + '/' + options.mid + '/'
|
||||
return streamDir
|
||||
}
|
||||
function removeSenstiveInfoFromMonitorConfig(monitorConfig){
|
||||
monitorConfig.protocol = ''
|
||||
monitorConfig.host = ''
|
||||
monitorConfig.path = ''
|
||||
monitorConfig.port = ''
|
||||
monitorConfig.details.muser = ''
|
||||
monitorConfig.details.mpass = ''
|
||||
monitorConfig.details.auto_host = ''
|
||||
monitorConfig.details.rtmp_key = ''
|
||||
monitorConfig.details.notes = ''
|
||||
monitorConfig.details.tv_channel_id = ''
|
||||
monitorConfig.details.tv_channel_group_title = ''
|
||||
monitorConfig.details.control_base_url = ''
|
||||
monitorConfig.details.cust_input = ''
|
||||
monitorConfig.details.cust_stream = ''
|
||||
monitorConfig.details.cust_snap = ''
|
||||
monitorConfig.details.cust_snap_raw = ''
|
||||
monitorConfig.details.cust_record = ''
|
||||
monitorConfig.details.cust_record = ''
|
||||
monitorConfig.details.cust_detect = ''
|
||||
monitorConfig.details.cust_detect_object = ''
|
||||
monitorConfig.details.cust_sip_record = ''
|
||||
monitorConfig.details.custom_output = ''
|
||||
monitorConfig.details.detector_cascades = ''
|
||||
monitorConfig.details.stream_channels = ''
|
||||
monitorConfig.details.input_maps = ''
|
||||
delete(monitorConfig.details.input_map_choices)
|
||||
delete(monitorConfig.details.substream)
|
||||
return monitorConfig
|
||||
}
|
||||
return {
|
||||
monitorStop,
|
||||
monitorIdle,
|
||||
|
@ -1809,5 +1853,6 @@ module.exports = (s,config,lang) => {
|
|||
getActiveViewerCount: getActiveViewerCount,
|
||||
setTimedActiveViewerForHttp: setTimedActiveViewerForHttp,
|
||||
attachMainProcessHandlers: attachMainProcessHandlers,
|
||||
removeSenstiveInfoFromMonitorConfig,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,11 @@ module.exports = function(s,config,lang){
|
|||
){
|
||||
require('./notifications/email.js')(s,config,lang,getSnapshot)
|
||||
}
|
||||
require('./notifications/webhook.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/emailByUser.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/discordBot.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/telegram.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/pushover.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/webhook.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/mqtt.js')(s,config,lang,getSnapshot)
|
||||
require('./notifications/matrix.js')(s,config,lang,getSnapshot)
|
||||
}
|
||||
|
|
|
@ -25,41 +25,33 @@ module.exports = function(s,config,lang,getSnapshot){
|
|||
})
|
||||
})
|
||||
}
|
||||
const doPostMethod = s.group[groupKey].init.global_webhook_method === 'post';
|
||||
const doPostMethod = s.group[groupKey].init.global_webhook_method === 'POST';
|
||||
// const includeSnapshot = s.group[groupKey].init.global_webhook_include_image === '1';
|
||||
const webhookInfoData = {
|
||||
info: sendBody,
|
||||
files: [],
|
||||
}
|
||||
if(files){
|
||||
const formData = new FormData();
|
||||
const formData = new FormData();
|
||||
if(files && doPostMethod){
|
||||
files.forEach(async (file,n) => {
|
||||
const fileName = file.name
|
||||
switch(file.type){
|
||||
case'video':
|
||||
// video cannot be sent this way unless POST
|
||||
if(doPostMethod){
|
||||
const fileName = file.name
|
||||
formData.append(`file${n + 1}`, file.attachment, {
|
||||
contentType: 'video/mp4',
|
||||
name: fileName,
|
||||
filename: fileName,
|
||||
});
|
||||
webhookInfoData.files.push(fileName)
|
||||
}
|
||||
formData.append(`file${n + 1}`, file.attachment, {
|
||||
contentType: 'video/mp4',
|
||||
name: fileName,
|
||||
filename: fileName,
|
||||
});
|
||||
webhookInfoData.files.push(fileName)
|
||||
break;
|
||||
case'photo':
|
||||
if(doPostMethod){
|
||||
const fileName = file.name
|
||||
formData.append(`file${n + 1}`, file.attachment, {
|
||||
contentType: 'image/jpeg',
|
||||
name: fileName,
|
||||
filename: fileName,
|
||||
});
|
||||
webhookInfoData.files.push(fileName)
|
||||
}else{
|
||||
const base64StringofImage = file.attachment.toString('base64')
|
||||
webhookInfoData.files.push(base64StringofImage)
|
||||
}
|
||||
formData.append(`file${n + 1}`, file.attachment, {
|
||||
contentType: 'image/jpeg',
|
||||
name: fileName,
|
||||
filename: fileName,
|
||||
});
|
||||
webhookInfoData.files.push(fileName)
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,16 +1,34 @@
|
|||
module.exports = function(s,config,lang,app,io){
|
||||
const {
|
||||
scanStatus,
|
||||
runOnvifScanner,
|
||||
stopOnvifScanner,
|
||||
} = require('./scanners/utils.js')(s,config,lang)
|
||||
const {
|
||||
ffprobe,
|
||||
} = require('./ffmpeg/utils.js')(s,config,lang)
|
||||
const {
|
||||
runOnvifScanner,
|
||||
} = require('./scanners/utils.js')(s,config,lang)
|
||||
const onWebSocketConnection = async (cn) => {
|
||||
const tx = function(z){if(!z.ke){z.ke=cn.ke;};cn.emit('f',z);}
|
||||
cn.on('f',(d) => {
|
||||
switch(d.f){
|
||||
case'onvif_scan_reconnect':
|
||||
tx({f: 'onvif_scan_current', devices: Object.values(scanStatus.allSuccessful), isScanning: scanStatus.isActive})
|
||||
break;
|
||||
case'onvif_stop':
|
||||
stopOnvifScanner()
|
||||
tx({f: 'onvif_scan_stopped'})
|
||||
break;
|
||||
case'onvif':
|
||||
runOnvifScanner(d,tx)
|
||||
const groupKey = `${cn.ke}`
|
||||
runOnvifScanner(d, (data) => {
|
||||
const response = { f: 'onvif', ...data }
|
||||
s.tx(response, 'GRP_' + cn.ke)
|
||||
}, (data) => {
|
||||
const response = { f: 'onvif', ff: 'failed_capture', ...data }
|
||||
s.tx(response, 'GRP_' + cn.ke)
|
||||
}).then((responseList) => {
|
||||
s.tx({ f: 'onvif_scan_complete' }, 'GRP_' + cn.ke)
|
||||
})
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
@ -40,4 +58,60 @@ module.exports = function(s,config,lang,app,io){
|
|||
})
|
||||
},res,req);
|
||||
})
|
||||
/**
|
||||
* API : ONVIF Scanner RUN
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/onvifScanner/:ke/scan',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Authorized']
|
||||
});
|
||||
return
|
||||
}
|
||||
const groupKey = req.params.ke;
|
||||
stopOnvifScanner()
|
||||
s.closeJsonResponse(res, { ok: true });
|
||||
},res,req);
|
||||
})
|
||||
/**
|
||||
* API : ONVIF Scanner STOP
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/onvifScanner/:ke/scan/stop',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Authorized']
|
||||
});
|
||||
return
|
||||
}
|
||||
const groupKey = req.params.ke;
|
||||
runOnvifScanner(d, (data) => {
|
||||
const response = { f: 'onvif', ...data }
|
||||
s.tx(response, 'GRP_' + groupKey)
|
||||
}, (data) => {
|
||||
const response = { f: 'onvif', ff: 'failed_capture', ...data }
|
||||
s.tx(response, 'GRP_' + groupKey)
|
||||
}).then((responseList) => {
|
||||
s.tx({ f: 'onvif_scan_complete' }, 'GRP_' + groupKey)
|
||||
s.closeJsonResponse(res, responseList)
|
||||
})
|
||||
},res,req);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,202 +1,377 @@
|
|||
var os = require('os');
|
||||
const async = require('async');
|
||||
const onvif = require("shinobi-onvif");
|
||||
const {
|
||||
addCredentialsToUrl,
|
||||
stringContains,
|
||||
getBuffer,
|
||||
} = require('../common.js')
|
||||
const scanStatus = {
|
||||
current: [],
|
||||
allSuccessful: {},
|
||||
cancelPromises: null,
|
||||
abortController: null
|
||||
};
|
||||
|
||||
module.exports = (s,config,lang) => {
|
||||
const ipRange = (start_ip, end_ip) => {
|
||||
var startLong = toLong(start_ip);
|
||||
var endLong = toLong(end_ip);
|
||||
if (startLong > endLong) {
|
||||
var tmp = startLong;
|
||||
startLong = endLong
|
||||
endLong = tmp;
|
||||
}
|
||||
var rangeArray = [];
|
||||
var i;
|
||||
for (i = startLong; i <= endLong; i++) {
|
||||
rangeArray.push(fromLong(i));
|
||||
}
|
||||
return rangeArray;
|
||||
}
|
||||
const portRange = (lowEnd,highEnd) => {
|
||||
var list = [];
|
||||
for (var i = lowEnd; i <= highEnd; i++) {
|
||||
const startLong = toLong(start_ip);
|
||||
const endLong = toLong(end_ip);
|
||||
if (startLong > endLong) {
|
||||
const tmp = startLong;
|
||||
startLong = endLong;
|
||||
endLong = tmp;
|
||||
}
|
||||
const rangeArray = [];
|
||||
for (let i = startLong; i <= endLong; i++) {
|
||||
rangeArray.push(fromLong(i));
|
||||
}
|
||||
return rangeArray;
|
||||
};
|
||||
|
||||
const portRange = (lowEnd, highEnd) => {
|
||||
const list = [];
|
||||
for (let i = lowEnd; i <= highEnd; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
//toLong taken from NPM package 'ip'
|
||||
const toLong = (ip) => {
|
||||
var ipl = 0;
|
||||
ip.split('.').forEach(function(octet) {
|
||||
ipl <<= 8;
|
||||
ipl += parseInt(octet);
|
||||
});
|
||||
return(ipl >>> 0);
|
||||
}
|
||||
//fromLong taken from NPM package 'ip'
|
||||
const fromLong = (ipl) => {
|
||||
return ((ipl >>> 24) + '.' +
|
||||
(ipl >> 16 & 255) + '.' +
|
||||
(ipl >> 8 & 255) + '.' +
|
||||
(ipl & 255) );
|
||||
}
|
||||
const runOnvifScanner = (options,foundCameraCallback) => {
|
||||
var ip = options.ip.replace(/ /g,'')
|
||||
var ports = options.port.replace(/ /g,'')
|
||||
if(options.ip === ''){
|
||||
var interfaces = os.networkInterfaces()
|
||||
var addresses = []
|
||||
for (var k in interfaces) {
|
||||
for (var k2 in interfaces[k]) {
|
||||
var address = interfaces[k][k2]
|
||||
if (address.family === 'IPv4' && !address.internal) {
|
||||
addresses.push(address.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
const addressRange = []
|
||||
addresses.forEach(function(address){
|
||||
if(address.indexOf('0.0.0')>-1){return false}
|
||||
var addressPrefix = address.split('.')
|
||||
delete(addressPrefix[3]);
|
||||
addressPrefix = addressPrefix.join('.')
|
||||
addressRange.push(`${addressPrefix}1-${addressPrefix}254`)
|
||||
})
|
||||
ip = addressRange.join(',')
|
||||
}
|
||||
if(ports === ''){
|
||||
ports = '80,8080,8000,7575,8081,9080,8090,8999,8899'
|
||||
}
|
||||
if(ports.indexOf('-') > -1){
|
||||
ports = ports.split('-')
|
||||
var portRangeStart = ports[0]
|
||||
var portRangeEnd = ports[1]
|
||||
ports = portRange(portRangeStart,portRangeEnd);
|
||||
}else{
|
||||
ports = ports.split(',')
|
||||
}
|
||||
var ipList = options.ipList
|
||||
var onvifUsername = options.user || ''
|
||||
var onvifPassword = options.pass || ''
|
||||
ip.split(',').forEach(function(addressRange){
|
||||
var ipRangeStart = addressRange[0]
|
||||
var ipRangeEnd = addressRange[1]
|
||||
if(addressRange.indexOf('-')>-1){
|
||||
addressRange = addressRange.split('-');
|
||||
ipRangeStart = addressRange[0]
|
||||
ipRangeEnd = addressRange[1]
|
||||
}else{
|
||||
ipRangeStart = addressRange
|
||||
ipRangeEnd = addressRange
|
||||
}
|
||||
if(!ipList){
|
||||
ipList = ipRange(ipRangeStart,ipRangeEnd);
|
||||
}else{
|
||||
ipList = ipList.concat(ipRange(ipRangeStart,ipRangeEnd))
|
||||
}
|
||||
})
|
||||
var hitList = []
|
||||
ipList.forEach((ipEntry,n) => {
|
||||
ports.forEach((portEntry,nn) => {
|
||||
hitList.push({
|
||||
xaddr : 'http://' + ipEntry + ':' + portEntry + '/onvif/device_service',
|
||||
user : onvifUsername,
|
||||
pass : onvifPassword,
|
||||
ip: ipEntry,
|
||||
port: portEntry,
|
||||
})
|
||||
})
|
||||
})
|
||||
var responseList = []
|
||||
hitList.forEach(async (camera) => {
|
||||
try{
|
||||
var device = new onvif.OnvifDevice(camera)
|
||||
var info = await device.init()
|
||||
var date = await device.services.device.getSystemDateAndTime()
|
||||
var stream = await device.services.media.getStreamUri({
|
||||
ProfileToken : device.current_profile.token,
|
||||
Protocol : 'RTSP'
|
||||
})
|
||||
};
|
||||
|
||||
var cameraResponse = {
|
||||
ip: camera.ip,
|
||||
port: camera.port,
|
||||
info: info,
|
||||
date: date,
|
||||
uri: stream.data.GetStreamUriResponse.MediaUri.Uri
|
||||
const toLong = (ip) => {
|
||||
let ipl = 0;
|
||||
ip.split('.').forEach(function(octet) {
|
||||
ipl <<= 8;
|
||||
ipl += parseInt(octet);
|
||||
});
|
||||
return (ipl >>> 0);
|
||||
};
|
||||
|
||||
const fromLong = (ipl) => {
|
||||
return ((ipl >>> 24) + '.' +
|
||||
(ipl >> 16 & 255) + '.' +
|
||||
(ipl >> 8 & 255) + '.' +
|
||||
(ipl & 255));
|
||||
};
|
||||
|
||||
const getNetworkAddresses = () => {
|
||||
const interfaces = os.networkInterfaces();
|
||||
const addresses = [];
|
||||
for (const k in interfaces) {
|
||||
for (const k2 in interfaces[k]) {
|
||||
const address = interfaces[k][k2];
|
||||
if (address.family === 'IPv4' && !address.internal) {
|
||||
addresses.push(address.address);
|
||||
}
|
||||
try{
|
||||
const camPtzConfigs = (await device.services.ptz.getConfigurations()).data.GetConfigurationsResponse
|
||||
if(
|
||||
camPtzConfigs.PTZConfiguration &&
|
||||
(
|
||||
camPtzConfigs.PTZConfiguration.PanTiltLimits ||
|
||||
camPtzConfigs.PTZConfiguration.ZoomLimits
|
||||
)
|
||||
){
|
||||
cameraResponse.isPTZ = true
|
||||
}
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
responseList.push(cameraResponse)
|
||||
var imageSnap
|
||||
try{
|
||||
const snapUri = addCredentialsToUrl({
|
||||
username: onvifUsername,
|
||||
password: onvifPassword,
|
||||
url: (await device.services.media.getSnapshotUri({
|
||||
ProfileToken : device.current_profile.token,
|
||||
})).data.GetSnapshotUriResponse.MediaUri.Uri,
|
||||
});
|
||||
imageSnap = (await getBuffer(snapUri)).toString('base64');
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
if(foundCameraCallback)foundCameraCallback(Object.assign(cameraResponse,{f: 'onvif', snapShot: imageSnap}))
|
||||
}catch(err){
|
||||
const searchError = (find) => {
|
||||
return stringContains(find,err.message,true)
|
||||
}
|
||||
var foundDevice = false
|
||||
var errorMessage = ''
|
||||
switch(true){
|
||||
//ONVIF camera found but denied access
|
||||
case searchError('400'): //Bad Request - Sender not Authorized
|
||||
foundDevice = true
|
||||
errorMessage = lang.ONVIFErr400
|
||||
break;
|
||||
case searchError('405'): //Method Not Allowed
|
||||
foundDevice = true
|
||||
errorMessage = lang.ONVIFErr405
|
||||
break;
|
||||
//Webserver exists but undetermined if IP Camera
|
||||
case searchError('404'): //Not Found
|
||||
foundDevice = true
|
||||
errorMessage = lang.ONVIFErr404
|
||||
break;
|
||||
}
|
||||
if(foundDevice && foundCameraCallback){
|
||||
foundCameraCallback({
|
||||
f: 'onvif',
|
||||
ff: 'failed_capture',
|
||||
ip: camera.ip,
|
||||
port: camera.port,
|
||||
error: errorMessage
|
||||
})
|
||||
}
|
||||
if(config.debugLogVerbose)s.debugLog(err);
|
||||
}
|
||||
})
|
||||
return responseList
|
||||
}
|
||||
return addresses;
|
||||
};
|
||||
|
||||
const getAddressRange = (addresses) => {
|
||||
const addressRange = [];
|
||||
addresses.forEach((address) => {
|
||||
if (address.indexOf('0.0.0') > -1) return;
|
||||
const addressPrefix = address.split('.').slice(0, 3).join('.');
|
||||
addressRange.push(`${addressPrefix}.1-${addressPrefix}.254`);
|
||||
});
|
||||
return addressRange.join(',');
|
||||
};
|
||||
|
||||
const getPorts = (ports) => {
|
||||
if (ports === '') {
|
||||
return '80,8080,8000,7575,8081,9080,8090,8999,8899'.split(',');
|
||||
}
|
||||
if (ports.indexOf('-') > -1) {
|
||||
const [start, end] = ports.split('-');
|
||||
return portRange(start, end);
|
||||
}
|
||||
return ports.split(',');
|
||||
};
|
||||
|
||||
const getIpList = (ip) => {
|
||||
const ipList = [];
|
||||
ip.split(',').forEach((range) => {
|
||||
const [start, end] = range.indexOf('-') > -1 ? range.split('-') : [range, range];
|
||||
ipList.push(...ipRange(start, end));
|
||||
});
|
||||
return ipList;
|
||||
};
|
||||
|
||||
const createHitList = (ipList, ports, onvifUsername = '', onvifPassword = '') => {
|
||||
const hitList = [];
|
||||
const usernameVariants = onvifUsername.split(',');
|
||||
const passwordVariants = onvifPassword.split(',');
|
||||
for (const username of usernameVariants) {
|
||||
for (const password of passwordVariants) {
|
||||
hitList.push(...ipList.flatMap((ipEntry) =>
|
||||
ports.map((portEntry) => ({
|
||||
xaddr: `http://${ipEntry}:${portEntry}/onvif/device_service`,
|
||||
user: username,
|
||||
pass: password,
|
||||
ip: ipEntry,
|
||||
port: portEntry,
|
||||
}))
|
||||
));
|
||||
}
|
||||
}
|
||||
return hitList;
|
||||
};
|
||||
|
||||
const takeSnapshot = async (cameraResponse, device) => {
|
||||
try {
|
||||
const snapUri = addCredentialsToUrl({
|
||||
username: cameraResponse.user,
|
||||
password: cameraResponse.pass,
|
||||
url: (await device.services.media.getSnapshotUri({ ProfileToken: device.current_profile.token })).data.GetSnapshotUriResponse.MediaUri.Uri,
|
||||
});
|
||||
const imgBuffer = await getBuffer(snapUri);
|
||||
cameraResponse.snapShot = imgBuffer.toString('base64');
|
||||
} catch (err) {
|
||||
console.error('Failed to get snapshot via ONVIF:', err);
|
||||
}
|
||||
return cameraResponse;
|
||||
}
|
||||
|
||||
const fetchCameraDetails = async (camera, onvifUsername, onvifPassword, foundCameraCallback, failedCameraCallback) => {
|
||||
const previousSuccess = scanStatus.allSuccessful[camera.ip];
|
||||
if (previousSuccess) {
|
||||
// console.log('FOUND PREVIOUS', camera.ip);
|
||||
foundCameraCallback(previousSuccess);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const device = new onvif.OnvifDevice(camera);
|
||||
const info = await device.init();
|
||||
const stream = await device.services.media.getStreamUri({
|
||||
ProfileToken: device.current_profile.token,
|
||||
Protocol: 'RTSP'
|
||||
});
|
||||
|
||||
const cameraResponse = {
|
||||
ip: camera.ip,
|
||||
port: camera.port,
|
||||
user: camera.user,
|
||||
pass: camera.pass,
|
||||
info: info,
|
||||
uri: stream.data.GetStreamUriResponse.MediaUri.Uri
|
||||
};
|
||||
|
||||
try {
|
||||
const camPtzConfigs = (await device.services.ptz.getConfigurations()).data.GetConfigurationsResponse;
|
||||
if (camPtzConfigs.PTZConfiguration && (camPtzConfigs.PTZConfiguration.PanTiltLimits || camPtzConfigs.PTZConfiguration.ZoomLimits)) {
|
||||
cameraResponse.isPTZ = true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(er)
|
||||
// s.debugLog(err);
|
||||
}
|
||||
await takeSnapshot(cameraResponse, device)
|
||||
scanStatus.allSuccessful[camera.ip] = cameraResponse;
|
||||
foundCameraCallback(cameraResponse);
|
||||
|
||||
return cameraResponse;
|
||||
} catch (err) {
|
||||
return handleCameraError(camera, err, failedCameraCallback);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCameraError = (camera, err, failedCameraCallback) => {
|
||||
const previousSuccess = scanStatus.allSuccessful[camera.ip];
|
||||
if (previousSuccess) {
|
||||
// console.log('FOUND PREVIOUS AFTER ERROR', camera.ip);
|
||||
return previousSuccess;
|
||||
}
|
||||
const searchError = (find) => stringContains(find, err.message, true);
|
||||
const commonIgnoredErrors = ['ECONNREFUSED', 'socket hang up'];
|
||||
let foundDevice = false;
|
||||
let errorMessage = '';
|
||||
|
||||
switch (true) {
|
||||
case searchError('ECONNREFUSED'):
|
||||
errorMessage = `ECONNREFUSED`;
|
||||
return {refused: true}
|
||||
break;
|
||||
case searchError('TIMEDOUT'):
|
||||
foundDevice = true;
|
||||
errorMessage = lang.ONVIFErr401;
|
||||
break;
|
||||
case searchError('401'):
|
||||
foundDevice = true;
|
||||
errorMessage = lang.ONVIFErr401;
|
||||
break;
|
||||
case searchError('400'):
|
||||
foundDevice = true;
|
||||
errorMessage = lang.ONVIFErr400;
|
||||
break;
|
||||
case searchError('405'):
|
||||
foundDevice = true;
|
||||
errorMessage = lang.ONVIFErr405;
|
||||
break;
|
||||
case searchError('404'):
|
||||
foundDevice = true;
|
||||
errorMessage = lang.ONVIFErr404;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (foundDevice) {
|
||||
const cameraResponse = {
|
||||
ip: camera.ip,
|
||||
port: camera.port,
|
||||
error: errorMessage,
|
||||
failedConnection: true,
|
||||
};
|
||||
failedCameraCallback(cameraResponse);
|
||||
return cameraResponse;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
function isValidOnvifResult(result) {
|
||||
return result.info || result.uri;
|
||||
}
|
||||
|
||||
function detectAndReplaceReolinkRTSP(camera, url){
|
||||
const possibilities = [`/h264Preview_01_main`, `/h265Preview_01_main`]
|
||||
for(possible of possibilities){
|
||||
// console.log(url, possible, url.indexOf(possible) > -1)
|
||||
if(url.indexOf(possible) > -1){
|
||||
return `rtmp://${camera.user}:${camera.pass}@${camera.ip}:1935/bcs/channel0_main.bcs?token=sdasdasd&channel=0&stream=0&user=${camera.user}&password=${camera.pass}`
|
||||
}
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const runOnvifScanner = async (options, foundCameraCallback, failedCameraCallback) => {
|
||||
if (scanStatus.isActive) return scanStatus.current;
|
||||
|
||||
scanStatus.isActive = true;
|
||||
scanStatus.abortController = new AbortController();
|
||||
const { signal } = scanStatus.abortController;
|
||||
const cancelPromises = [];
|
||||
scanStatus.cancelPromises = cancelPromises;
|
||||
let ip = options.ip.replace(/ /g, '');
|
||||
let ports = options.port.replace(/ /g, '');
|
||||
const onvifUsername = options.user || 'admin';
|
||||
const onvifPassword = options.pass || '';
|
||||
|
||||
if (ip === '') {
|
||||
const addresses = getNetworkAddresses();
|
||||
ip = getAddressRange(addresses);
|
||||
}
|
||||
|
||||
ports = getPorts(ports);
|
||||
|
||||
const ipList = getIpList(ip);
|
||||
const hitList = createHitList(ipList, ports, onvifUsername, onvifPassword);
|
||||
|
||||
const ipQueues = {};
|
||||
const responseList = [];
|
||||
const allPingSuccess = {};
|
||||
|
||||
const fetchWithTimeout = async (camera, onvifUsername, onvifPassword, foundCameraCallback, failedCameraCallback, signal) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error('Timeout')), 2500); // Adjust the timeout as needed
|
||||
fetchCameraDetails(camera, onvifUsername, onvifPassword, foundCameraCallback, failedCameraCallback, signal)
|
||||
.then(result => {
|
||||
clearTimeout(timeout);
|
||||
resolve(result);
|
||||
})
|
||||
.catch(error => {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
for (const camera of hitList) {
|
||||
if (!ipQueues[camera.ip]) {
|
||||
ipQueues[camera.ip] = async.queue(async (task) => {
|
||||
if (signal.aborted) {
|
||||
throw new Error('Aborted');
|
||||
}
|
||||
if(!scanStatus.allSuccessful[camera.ip]){
|
||||
const cameraIp = task.camera.ip;
|
||||
const hasPingSuccess = allPingSuccess[cameraIp];
|
||||
if (hasPingSuccess !== false) {
|
||||
const fetchPromise = fetchWithTimeout(task.camera, task.onvifUsername, task.onvifPassword, task.foundCameraCallback, task.failedCameraCallback, signal);
|
||||
cancelPromises.push(fetchPromise);
|
||||
const result = await fetchPromise;
|
||||
if (result.refused) allPingSuccess[cameraIp] = !result.refused;
|
||||
if (result.uri){
|
||||
result.uri = detectAndReplaceReolinkRTSP(task.camera, addCredentialsToUrl({ url: result.uri, username: task.camera.user, password: task.camera.pass }));
|
||||
}
|
||||
responseList.push({...result});
|
||||
}
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
ipQueues[camera.ip].push({
|
||||
camera,
|
||||
onvifUsername: camera.user,
|
||||
onvifPassword: camera.pass,
|
||||
foundCameraCallback,
|
||||
failedCameraCallback
|
||||
});
|
||||
}
|
||||
await Promise.all(Object.values(ipQueues).map(queue => new Promise((resolve) => queue.drain(resolve))));
|
||||
} catch (err) {
|
||||
if (err.message === 'Aborted') {
|
||||
console.log('Scan aborted');
|
||||
} else {
|
||||
console.error('big error', err);
|
||||
}
|
||||
}
|
||||
|
||||
scanStatus.isActive = false;
|
||||
scanStatus.abortController = null;
|
||||
scanStatus.cancelPromises = null;
|
||||
s.debugLog('Done Scan');
|
||||
return responseList;
|
||||
};
|
||||
|
||||
const stopOnvifScanner = () => {
|
||||
if (scanStatus.isActive && scanStatus.abortController) {
|
||||
scanStatus.abortController.abort();
|
||||
scanStatus.cancelPromises.forEach(promise => promise.catch(() => {}));
|
||||
scanStatus.isActive = false;
|
||||
s.debugLog('Scan stopped');
|
||||
}
|
||||
};
|
||||
|
||||
function expandIPRange(rangeStr) {
|
||||
const ipRangeToArray = (start, end) => {
|
||||
const startParts = start.split('.').map(Number);
|
||||
const endParts = end.split('.').map(Number);
|
||||
const ips = [];
|
||||
for (let a = startParts[0]; a <= endParts[0]; a++) {
|
||||
for (let b = startParts[1]; b <= endParts[1]; b++) {
|
||||
for (let c = startParts[2]; c <= endParts[2]; c++) {
|
||||
for (let d = startParts[3]; d <= endParts[3]; d++) {
|
||||
ips.push([a, b, c, d].join('.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ips;
|
||||
};
|
||||
|
||||
return rangeStr.split(',')
|
||||
.flatMap(range => {
|
||||
const [start, end] = range.split('-');
|
||||
return ipRangeToArray(start.trim(), end.trim());
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
ipRange: ipRange,
|
||||
portRange: portRange,
|
||||
runOnvifScanner: runOnvifScanner,
|
||||
}
|
||||
expandIPRange,
|
||||
ipRange,
|
||||
portRange,
|
||||
scanStatus,
|
||||
runOnvifScanner,
|
||||
stopOnvifScanner,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ module.exports = (config) => {
|
|||
getConfiguration: () => {
|
||||
return new Promise((resolve,reject) => {
|
||||
const configPath = config.thisIsDocker ? "/config/conf.json" : s.location.config;
|
||||
|
||||
|
||||
fs.readFile(configPath, 'utf8', (err, data) => {
|
||||
resolve(JSON.parse(data))
|
||||
});
|
||||
|
@ -33,11 +33,11 @@ module.exports = (config) => {
|
|||
},
|
||||
modifyConfiguration: (postBody) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(s.location.config)
|
||||
|
||||
console.log(config)
|
||||
|
||||
const configPath = config.thisIsDocker ? "/config/conf.json" : s.location.config;
|
||||
const configData = JSON.stringify(postBody,null,3);
|
||||
|
||||
|
||||
fs.writeFile(configPath, configData, resolve);
|
||||
});
|
||||
},
|
||||
|
|
|
@ -66,10 +66,17 @@ module.exports = (s,config,lang) => {
|
|||
const videosDirectory = s.getVideoDirectory(monitor)
|
||||
const tempDirectory = s.getStreamsDirectory(monitor)
|
||||
// const findCmd = [videosDirectory].concat(options.flags || ['-maxdepth','1'])
|
||||
fs.writeFileSync(
|
||||
tempDirectory + 'orphanCheck.sh',
|
||||
`find "${s.checkCorrectPathEnding(videosDirectory,true)}" -maxdepth 1 -type f -exec stat -c "%n" {} + | sort -r | head -n ${options.checkMax}`
|
||||
);
|
||||
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){
|
||||
console.log('Failed scanForOrphanedVideos', monitor.ke, monitor.mid)
|
||||
response.err = err.toString()
|
||||
resolve(response)
|
||||
return
|
||||
}
|
||||
let listing = spawn('sh',[tempDirectory + 'orphanCheck.sh'])
|
||||
// const onData = options.onData ? options.onData : () => {}
|
||||
const onError = options.onError ? options.onError : s.systemLog
|
||||
|
|
|
@ -54,6 +54,10 @@ module.exports = function(s,config,lang,io){
|
|||
if(config.renderPaths.grid === undefined){config.renderPaths.grid='pages/grid'}
|
||||
//slick.js (cycle) page
|
||||
if(config.renderPaths.cycle === undefined){config.renderPaths.cycle='pages/cycle'}
|
||||
//WallView page
|
||||
if(config.renderPaths.wallview === undefined){config.renderPaths.wallview='pages/wallview'}
|
||||
//WallVideoView page
|
||||
if(config.renderPaths.wallvideoview === undefined){config.renderPaths.wallvideoview='pages/wallvideoview'}
|
||||
// Use uws/cws
|
||||
if(config.useUWebsocketJs === undefined){config.useUWebsocketJs=true}
|
||||
if(config.webBlocksPreloaded === undefined){
|
||||
|
|
|
@ -311,7 +311,7 @@ module.exports = function(s,config,lang,app){
|
|||
} = s.checkPermission(user)
|
||||
if(
|
||||
userPermissions.monitor_create_disallowed ||
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed ||
|
||||
isRestrictedApiKey && apiKeyPermissions.edit_monitors_disallowed ||
|
||||
isRestricted && !monitorPermissions[`${monitorId}_monitor_edit`]
|
||||
){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
|
|
|
@ -31,6 +31,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
const {
|
||||
spawnSubstreamProcess,
|
||||
destroySubstreamProcess,
|
||||
removeSenstiveInfoFromMonitorConfig,
|
||||
} = require('./monitor/utils.js')(s,config,lang)
|
||||
const {
|
||||
sliceVideo,
|
||||
|
@ -771,6 +772,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
} = s.getMonitorsPermitted(user.details,monitorId)
|
||||
const {
|
||||
isRestricted,
|
||||
userPermissions,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user)
|
||||
|
@ -784,6 +786,7 @@ module.exports = function(s,config,lang,app,io){
|
|||
s.closeJsonResponse(res,[]);
|
||||
return
|
||||
}
|
||||
const cannotSeeImportantSettings = (isRestrictedApiKey && apiKeyPermissions.edit_monitors_disallowed) || userPermissions.monitor_create_disallowed;
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
|
@ -794,6 +797,12 @@ module.exports = function(s,config,lang,app,io){
|
|||
]
|
||||
},(err,r) => {
|
||||
r.forEach(function(v,n){
|
||||
const monitorId = v.mid;
|
||||
v.details = JSON.parse(v.details)
|
||||
var details = v.details;
|
||||
if(isRestricted && !monitorPermissions[`${monitorId}_monitor_edit`] || cannotSeeImportantSettings){
|
||||
r[n] = removeSenstiveInfoFromMonitorConfig(v);
|
||||
}
|
||||
if(s.group[v.ke] && s.group[v.ke].activeMonitors[v.mid]){
|
||||
const activeMonitor = s.group[v.ke].activeMonitors[v.mid]
|
||||
r[n].currentlyWatching = Object.keys(activeMonitor.watch).length
|
||||
|
@ -847,7 +856,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
}
|
||||
return streamURL
|
||||
}
|
||||
var details = JSON.parse(r[n].details);
|
||||
if(!details.tv_channel_id||details.tv_channel_id==='')details.tv_channel_id = 'temp_'+s.gid(5)
|
||||
if(details.snap==='1'){
|
||||
r[n].snapshot = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg'
|
||||
|
@ -1094,6 +1102,35 @@ module.exports = function(s,config,lang,app,io){
|
|||
},res,req);
|
||||
});
|
||||
/**
|
||||
* Page : Get Wall Video View (Wall Timeline)
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/wallvideoview/:ke', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const authKey = req.params.auth
|
||||
const groupKey = req.params.ke
|
||||
if(
|
||||
user.permissions.watch_videos === "0"
|
||||
&& user.details.allmonitors !== '1'
|
||||
){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
return
|
||||
}
|
||||
s.renderPage(req,res,config.renderPaths.wallvideoview,{
|
||||
forceUrlPrefix: req.query.host || '',
|
||||
data: req.params,
|
||||
protocol: req.protocol,
|
||||
baseUrl: req.protocol + '://' + req.hostname,
|
||||
config: s.getConfigWithBranding(req.hostname),
|
||||
define: s.getDefinitonFile(user.details ? user.details.lang : config.lang),
|
||||
lang: lang,
|
||||
$user: user,
|
||||
authKey: authKey,
|
||||
groupKey: groupKey,
|
||||
originalURL: s.getOriginalUrl(req)
|
||||
});
|
||||
},res,req);
|
||||
});
|
||||
/**
|
||||
* API : Get Events
|
||||
*/
|
||||
app.get([
|
||||
|
|
|
@ -415,4 +415,33 @@ module.exports = function(s,config,lang,app){
|
|||
},res,req);
|
||||
},res,req);
|
||||
});
|
||||
/**
|
||||
* Page : Get WallView
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/wallview/:ke', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const authKey = req.params.auth
|
||||
const groupKey = req.params.ke
|
||||
if(
|
||||
user.permissions.watch_stream === "0"
|
||||
&& user.details.allmonitors !== '1'
|
||||
){
|
||||
res.end(user.lang['Not Permitted'])
|
||||
return
|
||||
}
|
||||
s.renderPage(req,res,config.renderPaths.wallview,{
|
||||
forceUrlPrefix: req.query.host || '',
|
||||
data: req.params,
|
||||
protocol: req.protocol,
|
||||
baseUrl: req.protocol + '://' + req.hostname,
|
||||
config: s.getConfigWithBranding(req.hostname),
|
||||
define: s.getDefinitonFile(user.details ? user.details.lang : config.lang),
|
||||
lang: lang,
|
||||
$user: user,
|
||||
authKey: authKey,
|
||||
groupKey: groupKey,
|
||||
originalURL: s.getOriginalUrl(req)
|
||||
});
|
||||
},res,req);
|
||||
});
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
|
@ -33,20 +33,20 @@
|
|||
"googleapis": "^100.0.0",
|
||||
"http-proxy": "^1.18.1",
|
||||
"jsonfile": "^3.0.1",
|
||||
"knex": "^0.21.21",
|
||||
"knex": "^3.1.0",
|
||||
"ldapauth-fork": "^5.0.2",
|
||||
"marked": "^4.3.0",
|
||||
"moment": "^2.29.4",
|
||||
"mp4frag": "^0.6.1",
|
||||
"mqtt": "^4.3.7",
|
||||
"mysql": "^2.18.1",
|
||||
"mysql2": "^2.1.0",
|
||||
"mysql2": "^3.9.7",
|
||||
"node-abort-controller": "^3.0.1",
|
||||
"node-fetch": "^2.6.7",
|
||||
"node-onvif-events": "^2.0.5",
|
||||
"node-ssh": "^12.0.4",
|
||||
"node-telegram-bot-api": "^0.61.0",
|
||||
"node-telegram-bot-api": "^0.65.1",
|
||||
"nodemailer": "^6.7.1",
|
||||
"onvif": "^0.7.1",
|
||||
"pam-diff": "^1.1.0",
|
||||
"path": "^0.12.7",
|
||||
"pipe2pam": "^0.6.2",
|
||||
|
@ -54,7 +54,7 @@
|
|||
"pushover-notifications": "^1.2.2",
|
||||
"sat": "^0.7.1",
|
||||
"shinobi-node-moving-things-tracker": "^0.9.3",
|
||||
"shinobi-onvif": "0.1.9",
|
||||
"shinobi-onvif": "0.2.2",
|
||||
"shinobi-sound-detection": "^0.1.13",
|
||||
"shinobi-zwave": "^1.0.11",
|
||||
"smtp-server": "^3.13.0",
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
const fetch = require('node-fetch');
|
||||
const AdmZip = require('adm-zip');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
|
||||
const GITLAB_API_URL = 'https://gitlab.com/api/v4';
|
||||
const PROJECT_ID = 'Shinobi-Systems/shinobi-plugins';
|
||||
const BRANCH = 'master';
|
||||
|
||||
async function fetchAndDownloadFolder(folderName) {
|
||||
const url = `${GITLAB_API_URL}/projects/${encodeURIComponent(PROJECT_ID)}/repository/archive.zip?path=plugins/${folderName}&ref=${BRANCH}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Accept': 'application/zip',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download folder: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const buffer = await response.buffer();
|
||||
const zip = new AdmZip(buffer);
|
||||
|
||||
// Extract the ZIP to a temporary location
|
||||
const tempExtractPath = path.join(process.cwd(), 'temp_extracted');
|
||||
await fs.mkdir(tempExtractPath, { recursive: true });
|
||||
zip.extractAllTo(tempExtractPath, true);
|
||||
|
||||
// Find the folder ending with the target name
|
||||
const extractedFolder = (await fs.readdir(tempExtractPath)).find(dir => dir.endsWith(folderName));
|
||||
if (!extractedFolder) {
|
||||
throw new Error(`Folder ${folderName} not found in the extracted ZIP`);
|
||||
}
|
||||
|
||||
// Move the contents to the specified output path
|
||||
const sourcePath = path.join(tempExtractPath, extractedFolder, 'plugins', folderName);
|
||||
const targetPath = path.join(process.cwd(), 'plugins', `dl_${folderName}`);
|
||||
|
||||
// Ensure target path exists
|
||||
await fs.mkdir(targetPath, { recursive: true });
|
||||
|
||||
// Copy files from source to target
|
||||
const files = await fs.readdir(sourcePath);
|
||||
await Promise.all(files.map(async file => {
|
||||
const srcFile = path.join(sourcePath, file);
|
||||
const destFile = path.join(targetPath, file);
|
||||
await fs.rename(srcFile, destFile);
|
||||
}));
|
||||
|
||||
// Clean up temporary extraction directory
|
||||
await fs.rm(tempExtractPath, { recursive: true });
|
||||
|
||||
console.log(`Folder ${folderName} extracted to ${targetPath}`);
|
||||
} catch (error) {
|
||||
console.error('Error fetching and extracting folder:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command-line arguments
|
||||
const [folderName] = process.argv.slice(2);
|
||||
|
||||
if (!folderName) {
|
||||
console.error('Usage: node script.js <folderName>');
|
||||
process.exit(1);
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
const onvif = require('shinobi-onvif');
|
||||
const fs = require('fs');
|
||||
const { getDeviceInformation, mergeDeep, getFunctionParamNames } = require('./onvifUtilsForTest.js');
|
||||
|
||||
// Fetch command line arguments
|
||||
const args = process.argv.slice(2);
|
||||
const jsonFilePath = args[0];
|
||||
const streamType = args[1]; // 'main' or 'sub'
|
||||
const newBitrate = parseInt(args[2]); // Bitrate in kbps
|
||||
|
||||
if(!jsonFilePath || !streamType || !newBitrate){
|
||||
console.log(`node massBitrateUpdateOnvif.js ./exportedShinobiMonitors.json main 1500`)
|
||||
return
|
||||
}
|
||||
|
||||
// Read camera configurations from a JSON file
|
||||
const cameras = JSON.parse(fs.readFileSync(jsonFilePath, 'utf8'));
|
||||
|
||||
function tokenCheck(token){
|
||||
return streamType === 'sub' ?
|
||||
token.includes('sub')
|
||||
|| token.includes('Sub')
|
||||
|| token.includes('1')
|
||||
:
|
||||
token.includes('main')
|
||||
|| token.includes('Main')
|
||||
|| token.includes('0')
|
||||
}
|
||||
|
||||
// Function to update the bitrate
|
||||
const updateBitrate = async (camera, newBitrate, streamType) => {
|
||||
try {
|
||||
const device = new onvif.OnvifDevice({
|
||||
xaddr: `http://${camera.host}:${camera.details.onvif_port}/onvif/device_service`,
|
||||
user: camera.details.muser,
|
||||
pass: camera.details.mpass
|
||||
});
|
||||
|
||||
await device.init();
|
||||
|
||||
// Determine which stream type to update
|
||||
const configs = (await device.services.media.getVideoEncoderConfigurations()).data.GetVideoEncoderConfigurationsResponse.Configurations;
|
||||
const config = configs.find(c => tokenCheck(c.$.token));
|
||||
|
||||
if (!config) {
|
||||
console.log(`No ${streamType} stream configuration found for camera ${camera.host}`);
|
||||
return;
|
||||
}
|
||||
const chosenToken = config.$.token;
|
||||
const {
|
||||
videoEncoders,
|
||||
videoEncoderOptions
|
||||
} = await getDeviceInformation(device,{
|
||||
videoEncoders: true,
|
||||
videoEncoderOptions: true
|
||||
});
|
||||
const videoEncoderIndex = {};
|
||||
videoEncoders.forEach((encoder) => {videoEncoderIndex[encoder.$.token] = encoder});
|
||||
const videoEncoder = videoEncoderIndex[chosenToken] || {};
|
||||
const theChanges = {
|
||||
RateControl: {
|
||||
BitrateLimit: newBitrate
|
||||
}
|
||||
};
|
||||
const onvifParams = mergeDeep(videoEncoder,theChanges);
|
||||
const newConfig = {
|
||||
ConfigurationToken: config.$.token,
|
||||
Configuration: onvifParams
|
||||
};
|
||||
if(newBitrate != videoEncoder.RateControl.BitrateLimit){
|
||||
console.log(videoEncoder)
|
||||
const updateResponse = await device.services.media.setVideoEncoderConfiguration(newConfig);
|
||||
console.log('updateResponse',updateResponse.data)
|
||||
console.log(`Updated ${streamType} stream bitrate for camera ${camera.host} to ${newBitrate} kbps`);
|
||||
}else{
|
||||
console.log(`Skipped ${camera.host}`)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Error updating bitrate for camera ${camera.host}:`, err);
|
||||
}
|
||||
};
|
||||
|
||||
// Iterate over cameras sequentially and update bitrate for the specified stream type
|
||||
const updateAllCameras = async () => {
|
||||
for (const camera of cameras) {
|
||||
await updateBitrate(camera, newBitrate, streamType);
|
||||
}
|
||||
};
|
||||
|
||||
updateAllCameras();
|
|
@ -0,0 +1,546 @@
|
|||
// relies on https://gitlab.com/Shinobi-Systems/shinobi-onvif
|
||||
const onvif = require('shinobi-onvif');
|
||||
const getFunctionParamNames = function(func) {
|
||||
var fnStr = func.toString().replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg, '');
|
||||
var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(/([^\s,]+)/g);
|
||||
if(result === null)
|
||||
result = [];
|
||||
return result;
|
||||
}
|
||||
const mergeDeep = function(...objects) {
|
||||
const isObject = obj => obj && typeof obj === 'object';
|
||||
|
||||
return objects.reduce((prev, obj) => {
|
||||
Object.keys(obj).forEach(key => {
|
||||
const pVal = prev[key];
|
||||
const oVal = obj[key];
|
||||
|
||||
if (Array.isArray(pVal) && Array.isArray(oVal)) {
|
||||
prev[key] = pVal.concat(...oVal);
|
||||
}
|
||||
else if (isObject(pVal) && isObject(oVal)) {
|
||||
prev[key] = mergeDeep(pVal, oVal);
|
||||
}
|
||||
else {
|
||||
prev[key] = oVal;
|
||||
}
|
||||
});
|
||||
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
const replaceDynamicInOptions = (Camera,options) => {
|
||||
const newOptions = {}
|
||||
Object.keys(options).forEach((key) => {
|
||||
const value = options[key]
|
||||
if(typeof value === 'string'){
|
||||
newOptions[key] = value.replace(/__CURRENT_TOKEN/g,Camera.current_profile.token)
|
||||
}else if(value !== undefined && value !== null){
|
||||
newOptions[key] = value
|
||||
}
|
||||
})
|
||||
return newOptions
|
||||
}
|
||||
const runOnvifMethod = async (onvifOptions) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
const onvifDevice = onvifOptions.device
|
||||
var response = {ok: false}
|
||||
var errorMessage = function(msg,error){
|
||||
response.ok = false
|
||||
response.msg = msg
|
||||
response.error = error
|
||||
resolve(response)
|
||||
}
|
||||
var actionCallback = function(onvifActionResponse){
|
||||
response.ok = true
|
||||
if(onvifActionResponse.data){
|
||||
response.responseFromDevice = onvifActionResponse.data
|
||||
}else{
|
||||
response.responseFromDevice = onvifActionResponse
|
||||
}
|
||||
if(onvifActionResponse.soap)response.soap = onvifActionResponse.soap
|
||||
resolve(response)
|
||||
}
|
||||
var isEmpty = function(obj) {
|
||||
for(var key in obj) {
|
||||
if(obj.hasOwnProperty(key))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
var completeAction = function(command){
|
||||
if(command && command.then){
|
||||
command.then(actionCallback).catch(function(error){
|
||||
errorMessage('Device Action responded with an error',error)
|
||||
})
|
||||
}else if(command){
|
||||
response.ok = true
|
||||
response.repsonseFromDevice = command
|
||||
resolve(response)
|
||||
}else{
|
||||
response.error = 'Big Errors, Please report it to Shinobi Development'
|
||||
resolve(response)
|
||||
}
|
||||
}
|
||||
var action
|
||||
if(onvifOptions.service){
|
||||
if(onvifDevice.services[onvifOptions.service] === undefined){
|
||||
return errorMessage('This is not an available service. Please use one of the following : '+Object.keys(onvifDevice.services).join(', '))
|
||||
}
|
||||
if(onvifDevice.services[onvifOptions.service] === null){
|
||||
return errorMessage('This service is not activated. Maybe you are not connected through ONVIF. You can test by attempting to use the "Control" feature with ONVIF in Shinobi.')
|
||||
}
|
||||
action = onvifDevice.services[onvifOptions.service][onvifOptions.action]
|
||||
}else{
|
||||
action = onvifDevice[onvifOptions.action]
|
||||
}
|
||||
if(!action || typeof action !== 'function'){
|
||||
errorMessage(onvifOptions.action+' is not an available ONVIF function. See https://github.com/futomi/node-onvif for functions.')
|
||||
}else{
|
||||
var argNames = getFunctionParamNames(action)
|
||||
var options
|
||||
var command
|
||||
if(argNames[0] === 'options' || argNames[0] === 'params'){
|
||||
options = replaceDynamicInOptions(onvifDevice,onvifOptions.options || {})
|
||||
response.options = options
|
||||
}
|
||||
if(onvifOptions.service){
|
||||
command = onvifDevice.services[onvifOptions.service][onvifOptions.action](options)
|
||||
}else{
|
||||
command = onvifDevice[onvifOptions.action](options)
|
||||
}
|
||||
completeAction(command)
|
||||
}
|
||||
})
|
||||
}
|
||||
const getDeviceInformation = async (onvifDevice,options) => {
|
||||
// const options = {
|
||||
// protocols: true,
|
||||
// networkInterface: true,
|
||||
// gateway: true,
|
||||
// dns: true,
|
||||
// ntp: true,
|
||||
// }
|
||||
const response = {
|
||||
ok: true,
|
||||
}
|
||||
try{
|
||||
if(options.all || options.protocols){
|
||||
response.protocols = (await onvifDevice.services.device.getNetworkProtocols()).data.GetNetworkProtocolsResponse.NetworkProtocols
|
||||
}
|
||||
if(options.all || options.networkInterface){
|
||||
response.networkInterface = (await onvifDevice.services.device.getNetworkInterfaces()).data.GetNetworkInterfacesResponse.NetworkInterfaces
|
||||
}
|
||||
if(options.all || options.gatewayAddress){
|
||||
response.gateway = (await onvifDevice.services.device.getNetworkDefaultGateway()).data.GetNetworkDefaultGatewayResponse.NetworkGateway.IPv4Address
|
||||
}
|
||||
if(options.all || options.dns){
|
||||
response.dns = (await onvifDevice.services.device.getDNS()).data.GetDNSResponse.DNSInformation
|
||||
}
|
||||
if(options.all || options.ntp){
|
||||
response.ntp = (await onvifDevice.services.device.getNTP()).data.GetNTPResponse.NTPInformation
|
||||
}
|
||||
if(options.all || options.date){
|
||||
response.date = (await onvifDevice.services.device.getSystemDateAndTime()).data.GetSystemDateAndTimeResponse.SystemDateAndTime
|
||||
}
|
||||
if(options.all || options.users){
|
||||
response.users = (await onvifDevice.services.device.getUsers()).data.GetUsersResponse.User
|
||||
}
|
||||
if(options.all || options.hostname){
|
||||
response.hostname = (await onvifDevice.services.device.getHostname()).data.GetHostnameResponse.HostnameInformation.Name
|
||||
}
|
||||
if(options.all || options.discoveryMode){
|
||||
response.discoveryMode = (await onvifDevice.services.device.getDiscoveryMode()).data.GetDiscoveryModeResponse.DiscoveryMode
|
||||
}
|
||||
if(options.all || options.videoEncoders){
|
||||
response.videoEncoders = (await onvifDevice.services.media.getVideoEncoderConfigurations()).data.GetVideoEncoderConfigurationsResponse.Configurations
|
||||
}
|
||||
if(options.all || options.videoEncoderOptions){
|
||||
response.videoEncoderOptions = (await runOnvifMethod({
|
||||
device: onvifDevice,
|
||||
action: 'getVideoEncoderConfigurationOptions',
|
||||
service: 'media',
|
||||
options: {}
|
||||
})).responseFromDevice.GetVideoEncoderConfigurationOptionsResponse.Options
|
||||
}
|
||||
if(options.all || options.imagingSettings){
|
||||
const imagingSettings = (await runOnvifMethod({
|
||||
device: onvifDevice,
|
||||
action: 'getImagingSettings',
|
||||
service: 'imaging',
|
||||
options: {"ConfigurationToken":"__CURRENT_TOKEN"}
|
||||
}));
|
||||
response.imagingSettings = imagingSettings.responseFromDevice ? imagingSettings.responseFromDevice.GetImagingSettingsResponse.ImagingSettings : imagingSettings
|
||||
}
|
||||
}catch(err){
|
||||
response.ok = false
|
||||
response.error = err.stack.toString()
|
||||
console.log(err)
|
||||
console.log(onvifDevice)
|
||||
}
|
||||
return response
|
||||
}
|
||||
const setProtocols = async (onvifDevice,saveSet) => {
|
||||
// const saveSet = {
|
||||
// "HTTP": "80",
|
||||
// "RTSP": "554",
|
||||
// }
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const saveKeys = Object.keys(saveSet)
|
||||
const protocols = (await getDeviceInformation(onvifDevice,{protocols: true})).protocols
|
||||
protocols.forEach((item) => {
|
||||
saveKeys.forEach((key) => {
|
||||
if(item.Name === key.toUpperCase()){
|
||||
const port = saveSet[key] && !isNaN(saveSet[key]) ? parseInt(saveSet[key]) : item.Name === 'RTSP' ? 554 : 80
|
||||
console.log('PORT',port)
|
||||
item.Port = port
|
||||
item.Enabled = true
|
||||
}
|
||||
})
|
||||
})
|
||||
console.log('protocols',protocols)
|
||||
|
||||
const onvifResponse = await onvifDevice.services.device.setNetworkProtocols({
|
||||
NetworkProtocols: protocols
|
||||
})
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const setNetworkInterface = async (onvifDevice,options) => {
|
||||
// const options = {
|
||||
// interface: 'eth0',
|
||||
// ipv4: '10.1.103.158',
|
||||
// dhcp: false,
|
||||
// }
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const { networkInterface } = await getDeviceInformation(onvifDevice,{networkInterface: true})
|
||||
const onvifParams = {
|
||||
SetNetworkInterfaces: {
|
||||
InterfaceToken: options.interface || networkInterface.$.token,
|
||||
NetworkInterface: {
|
||||
Enabled: true,
|
||||
IPv4: {
|
||||
Enabled: true,
|
||||
Manual: {
|
||||
Address: options.ipv4 || networkInterface.IPv4.Config.Manual.Address,
|
||||
PrefixLength: '24',
|
||||
},
|
||||
'DHCP': options.dhcp === undefined || options.dhcp === null ? networkInterface.IPv4.Config.DHCP === 'true' ? true : false : options.dhcp,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const onvifResponse = await onvifDevice.services.device.setNetworkInterfaces(onvifParams)
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const setGateway = async (onvifDevice,options) => {
|
||||
// const options = {
|
||||
// ipv4: '1.1.1.1,8.8.8.8',
|
||||
// }
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const gatewayAddress = (await getDeviceInformation(onvifDevice,{gateway: true})).gateway
|
||||
const onvifResponse = await onvifDevice.services.device.setNetworkDefaultGateway({
|
||||
'NetworkGateway': [
|
||||
{'IPv4Address': options.ipv4 || gatewayAddress}
|
||||
]
|
||||
})
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const setDNS = async (onvifDevice,options) => {
|
||||
options = Object.assign({
|
||||
dns: '1.1.1.1,8.8.8.8',
|
||||
searchDomain: 'localhost',
|
||||
dhcp: false,
|
||||
},options)
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const dnsArray = []
|
||||
const searchDomain = (options.searchDomain || '').split(',')
|
||||
const dnsList = (options.dns || '1.1.1.1').split(',')
|
||||
dnsList.forEach((item) => {
|
||||
dnsArray.push({
|
||||
Type: 'IPv4',
|
||||
IPv4Address: item,
|
||||
})
|
||||
})
|
||||
const dnsInfo = (await getDeviceInformation(onvifDevice,{dns: true})).dns
|
||||
const onvifResponse = await onvifDevice.services.device.setDNS({
|
||||
'FromDHCP' : !options.dhcp ? false : true,
|
||||
'SearchDomain': searchDomain,
|
||||
'DNSManual' : dnsArray
|
||||
})
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const setNTP = async (onvifDevice,options) => {
|
||||
// const options = {
|
||||
// ipv4: '1.1.1.1,8.8.8.8',
|
||||
// dhcp: false,
|
||||
// }
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const ntpInfo = (await getDeviceInformation(onvifDevice,{ntp: true})).ntp
|
||||
const ipv4 = options.ipv4 || ntpInfo.NTPManual.IPv4Address
|
||||
const onvifResponse = await onvifDevice.services.device.setNTP({
|
||||
FromDHCP: !options.dhcp ? false : true,
|
||||
NTPManual: {'Type': "IPv4", 'IPv4Address': ipv4}
|
||||
})
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const setHostname = async (onvifDevice,options) => {
|
||||
// const options = {
|
||||
// name: 'hostname',
|
||||
// }
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const hostname = options.name || (await getDeviceInformation(onvifDevice,{hostname: true})).hostname
|
||||
const onvifResponse = await onvifDevice.services.device.setHostname({
|
||||
Name: hostname
|
||||
})
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const rebootCamera = async (onvifDevice,options) => {
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const onvifResponse = await onvifDevice.services.device.reboot()
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const setDateAndTime = async (onvifDevice,options) => {
|
||||
// const options = {
|
||||
// dateTimeType: 'ntp',
|
||||
// daylightSavings: false,
|
||||
// timezone: 'XXXX',
|
||||
// utcDateTime: 'XXXX',
|
||||
// }
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
// const dateInfo = await onvifDevice.services.device.getSystemDateAndTime().GetSystemDateAndTimeResponse.SystemDateAndTime
|
||||
const onvifResponse = await onvifDevice.services.device.setSystemDateAndTime ({
|
||||
DateTimeType: options.dateTimeType,
|
||||
DaylightSavings: !options.daylightSavings ? false : true,
|
||||
// TimeZone: options.timezone,
|
||||
UTCDateTime: new Date(options.utcDateTime),
|
||||
})
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const createUser = async (onvifDevice,options) => {
|
||||
// const options = {
|
||||
// name: 'user1',
|
||||
// password: '123',
|
||||
// level: 'Administrator' || 'Operator' || 'User',
|
||||
// }
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const onvifResponse = await onvifDevice.services.device.createUsers({
|
||||
'User' : [
|
||||
{'Username': options.name, 'Password' : options.password, 'UserLevel': options.level}
|
||||
]
|
||||
})
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const deleteUser = async (onvifDevice,options) => {
|
||||
// const options = {
|
||||
// name: 'user1',
|
||||
// }
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const onvifResponse = await onvifDevice.services.device.deleteUsers({
|
||||
'User' : [
|
||||
{'Username': options.name}
|
||||
]
|
||||
})
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const validateEncoderOptions = (chosenOptions, videoEncoderOptions) => {
|
||||
const resolutions = []
|
||||
const minQuality = parseInt(videoEncoderOptions.QualityRange.Min) || 1
|
||||
const maxQuality = parseInt(videoEncoderOptions.QualityRange.Max) || 6
|
||||
const quality = !isNaN(chosenOptions.Quality) ? parseInt(chosenOptions.Quality) : 1
|
||||
const minGovLength = parseInt(videoEncoderOptions.H264.GovLengthRange.Min) || 0
|
||||
const maxGovLength = parseInt(videoEncoderOptions.H264.GovLengthRange.Max) || 100
|
||||
const govLength = !isNaN(chosenOptions.H264.GovLength) ? parseInt(chosenOptions.H264.GovLength) : 10
|
||||
const minFrameRateLimit = parseInt(videoEncoderOptions.H264.FrameRateRange.Min) || 1
|
||||
const maxFrameRateLimit = parseInt(videoEncoderOptions.H264.FrameRateRange.Max) || 30
|
||||
const frameRateLimit = !isNaN(chosenOptions.RateControl.FrameRateLimit) ? parseInt(chosenOptions.RateControl.FrameRateLimit) : 15
|
||||
const minEncodingInterval = parseInt(videoEncoderOptions.H264.EncodingIntervalRange.Min) || 1
|
||||
const maxEncodingInterval = parseInt(videoEncoderOptions.H264.EncodingIntervalRange.Max) || 30
|
||||
const encodingInterval = !isNaN(chosenOptions.RateControl.EncodingInterval) ? parseInt(chosenOptions.RateControl.EncodingInterval) : 1
|
||||
videoEncoderOptions.H264.ResolutionsAvailable.forEach((resolution) => {
|
||||
resolutions.push(`${resolution.Width}x${resolution.Height}`)
|
||||
})
|
||||
if(resolutions.indexOf(`${chosenOptions.Resolution.Width}x${chosenOptions.Resolution.Height}`) === -1){
|
||||
chosenOptions.Resolution.Width = resolutions[0].Width
|
||||
chosenOptions.Resolution.Height = resolutions[0].Height
|
||||
}
|
||||
if(quality < minQuality || quality > maxQuality){
|
||||
chosenOptions.Quality = 1
|
||||
}
|
||||
if(govLength < minGovLength || govLength > maxGovLength){
|
||||
chosenOptions.H264.GovLength = 10
|
||||
}
|
||||
if(frameRateLimit < minFrameRateLimit || frameRateLimit > maxFrameRateLimit){
|
||||
chosenOptions.RateControl.FrameRateLimit = 15
|
||||
}
|
||||
if(encodingInterval < minEncodingInterval || encodingInterval > maxEncodingInterval){
|
||||
chosenOptions.RateControl.EncodingInterval = 1
|
||||
}
|
||||
}
|
||||
const setVideoConfiguration = async (onvifDevice,options) => {
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const {
|
||||
videoEncoders,
|
||||
videoEncoderOptions
|
||||
} = await getDeviceInformation(onvifDevice,{
|
||||
videoEncoders: true,
|
||||
videoEncoderOptions: true
|
||||
})
|
||||
const videoEncoderIndex = {}
|
||||
videoEncoders.forEach((encoder) => {videoEncoderIndex[encoder.$.token] = encoder})
|
||||
const chosenToken = `${options.videoToken || videoEncoders[0].$.token}`
|
||||
const videoEncoder = videoEncoderIndex[chosenToken] || {}
|
||||
const onvifParams = mergeDeep(videoEncoder,options,{
|
||||
Multicast: {
|
||||
AutoStart: !options.Multicast || !options.Multicast.AutoStart ? 'false' : 'true'
|
||||
},
|
||||
})
|
||||
const validatedEncoderOptions = validateEncoderOptions(onvifParams, videoEncoderOptions)
|
||||
const onvifResponse = await onvifDevice.services.media.setVideoEncoderConfiguration({
|
||||
Configuration: onvifParams,
|
||||
ConfigurationToken: chosenToken
|
||||
})
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const setImagingSettings = async (onvifDevice,options) => {
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const { imagingSettings } = await getDeviceInformation(onvifDevice,{imagingSettings: true})
|
||||
const onvifParams = mergeDeep(imagingSettings,options)
|
||||
const onvifResponse = await onvifDevice.services.imaging.setImagingSettings({
|
||||
ConfigurationToken: options.videoToken || onvifDevice.current_profile.token,
|
||||
ImagingSettings: onvifParams
|
||||
})
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const setDiscoveryMode = async (onvifDevice,options) => {
|
||||
const response = {
|
||||
ok: false
|
||||
}
|
||||
try{
|
||||
const onvifResponse = await onvifDevice.services.device.setDiscoveryMode(options.mode === 'Discoverable' || options.mode === 'NonDiscoverable' ? options.mode : 'Discoverable')
|
||||
response.ok = true
|
||||
response.onvifResponse = onvifResponse
|
||||
}catch(err){
|
||||
response.error = err.stack.toString()
|
||||
}
|
||||
return response
|
||||
}
|
||||
const getUIFieldValues = async (onvifDevice) => {
|
||||
return await getDeviceInformation(onvifDevice,{
|
||||
all: true,
|
||||
})
|
||||
}
|
||||
module.exports = {
|
||||
mergeDeep,
|
||||
getFunctionParamNames,
|
||||
getDeviceInformation: getDeviceInformation,
|
||||
setHostname: setHostname,
|
||||
setProtocols: setProtocols,
|
||||
setGateway: setGateway,
|
||||
setDNS: setDNS,
|
||||
setNTP: setNTP,
|
||||
rebootCamera: rebootCamera,
|
||||
setDateAndTime: setDateAndTime,
|
||||
createUser: createUser,
|
||||
deleteUser: deleteUser,
|
||||
setVideoConfiguration: setVideoConfiguration,
|
||||
setImagingSettings: setImagingSettings,
|
||||
setDiscoveryMode: setDiscoveryMode,
|
||||
setNetworkInterface: setNetworkInterface,
|
||||
getUIFieldValues: getUIFieldValues,
|
||||
}
|
|
@ -3,12 +3,22 @@
|
|||
background-size: cover;
|
||||
height: 100px;
|
||||
}
|
||||
#tab-onvifScanner .onvif_result .card {
|
||||
/* #tab-onvifScanner .onvif_result .card {
|
||||
cursor: pointer;
|
||||
}
|
||||
} */
|
||||
#tab-onvifScanner .onvif_result .card-body {
|
||||
min-height: auto;
|
||||
}
|
||||
#tab-onvifScanner .onvif_result {
|
||||
padding-top:2rem;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
#tab-onvifScanner .onvif_result .uri{
|
||||
word-break: break-word;
|
||||
}
|
||||
#tab-onvifScanner .scan-item-img {
|
||||
width: 100px;
|
||||
border-radius: 7px;
|
||||
height: 100px;
|
||||
background-size: cover;
|
||||
background-color: #1f80f9;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
#wallview-container {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
#wallview-canvas,body {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#wallview-controls{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
padding: 5px;
|
||||
opacity: 0;
|
||||
transition: 0.2s;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
#wallview-controls:hover{
|
||||
opacity: 100%;
|
||||
background: rgba(0,0,0,0.2)
|
||||
}
|
||||
|
||||
#wallview-info-screen{
|
||||
z-index: 10;
|
||||
background-color: rgba(0,0,0,0.6);
|
||||
position: fixed;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#wallview-monitorList i{
|
||||
display: none;
|
||||
}
|
||||
|
||||
#wallview-monitorList .active i{
|
||||
display: inlin-block;
|
||||
}
|
||||
|
||||
#wallview-canvas .wallview-video {
|
||||
position: absolute;
|
||||
}
|
||||
#wallview-canvas .wallview-video .overlay {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
#wallview-canvas .wallview-video iframe {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
#wallview-canvas .wallview-video.col-md-4 {
|
||||
height:30vh;
|
||||
}
|
||||
#wallview-canvas .wallview-video.col-md-6 {
|
||||
height: 40vh;
|
||||
}
|
||||
#wallview-canvas .wallview-video.col-md-12 {
|
||||
height: 80vh;
|
||||
margin-bottom: 0.5rem !important;
|
||||
}
|
||||
#wallview-canvas .wallview-video:not(.no-video){
|
||||
background-color: #000!important;
|
||||
}
|
||||
#wallview-canvas .wallview-video.no-video{
|
||||
display: none;
|
||||
}
|
||||
#wallview-canvas.show-non-playing .wallview-video.no-video{
|
||||
display: flex;
|
||||
}
|
||||
#wallview-canvas .wallview-video .wallview-item-controls{
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
opacity: 0;
|
||||
}
|
||||
#wallview-canvas .wallview-video:hover .wallview-item-controls{
|
||||
opacity: 0.5;
|
||||
}
|
|
@ -35,6 +35,12 @@
|
|||
#easyRemoteAccess .card.active .fa {
|
||||
color: #6ee068;
|
||||
}
|
||||
#easyRemoteAccess .card .remote-dashboard-link {
|
||||
display: none!important;
|
||||
}
|
||||
#easyRemoteAccess .card.active .remote-dashboard-link {
|
||||
display: block!important;
|
||||
}
|
||||
#easyRemoteAccess .card .selected-badge {
|
||||
display: none;
|
||||
color: #fff!important;
|
||||
|
@ -56,3 +62,7 @@
|
|||
#p2pServerList .card .d-flex.flex-row:nth-of-type(odd) {
|
||||
background: rgba(0,0,0,0.1)
|
||||
}
|
||||
|
||||
#easyRemoteAccess .active .activate-remote-selection .fa-check:before {
|
||||
content: "\f00d"!important;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
$(document).ready(function(){
|
||||
var alternateLoginsBox = $('#alternate-logins')
|
||||
function getAlternateLogins(){
|
||||
$.get(getApiPrefix('loginTokens'),function(data){
|
||||
var rows = data.rows
|
||||
alternateLoginsBox.empty()
|
||||
if(rows.length > 0){
|
||||
$.each(rows,function(n,row){
|
||||
alternateLoginsBox.append(`<div class="row" login-id="${row.loginId}" style="border-bottom: 1px solid #333;padding-top:1rem;padding-bottom:1rem;display:flex">
|
||||
<div class="col-md-3" style="flex: 3">
|
||||
<i class="fa fa-address-card"></i>
|
||||
<span class="epic-text text-white">${row.type}</span>
|
||||
</div>
|
||||
<div class="col-md-3" style="flex: 3">
|
||||
<div>${row.name}</div>
|
||||
</div>
|
||||
<div class="col-md-3" style="flex: 3">
|
||||
<div title="${lang.lastLogin}">${moment(row.lastLogin).format('YYYY-MM-DD hh:mm:ss A')}</div>
|
||||
</div>
|
||||
<div class="col-md-3 text-right" style="flex: 3">
|
||||
<a class="btn badge-sm badge btn-danger unlink-account"><i class="fa fa-unlink"></i> ${lang.Unlink}</a>
|
||||
</div>
|
||||
</div>`)
|
||||
})
|
||||
}else{
|
||||
alternateLoginsBox.append(`<div class="row">
|
||||
<div class="col-md-12 text-center epic-text" style="margin: 0">
|
||||
${lang.noLoginTokensAdded}
|
||||
</div>
|
||||
</div>`)
|
||||
}
|
||||
})
|
||||
}
|
||||
getAlternateLogins()
|
||||
alternateLoginsBox.on('click','.unlink-account',function(){
|
||||
var loginId = $(this).parents('[login-id]').attr('login-id')
|
||||
$.confirm.create({
|
||||
title: lang['Unlink Login'],
|
||||
body: lang.noUndoForAction,
|
||||
clickOptions: {
|
||||
title: lang['Unlink'],
|
||||
class: 'btn-danger'
|
||||
},
|
||||
clickCallback: function(){
|
||||
$.get(getApiPrefix('loginTokens') + '/' + loginId + '/delete',function(data){
|
||||
if(data.ok){
|
||||
new PNotify({
|
||||
title: lang.Unlinked,
|
||||
text: lang.loginHandleUnbound,
|
||||
type: 'success'
|
||||
})
|
||||
alternateLoginsBox.find(`[login-id="${loginId}"]`).remove()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
window.drawAlternateLoginsToSettings = getAlternateLogins
|
||||
})
|
|
@ -0,0 +1,180 @@
|
|||
$(document).ready(function(){
|
||||
onInitWebsocket(function(){
|
||||
loadMonitorsIntoMemory(function(data){
|
||||
setInterfaceCounts(data)
|
||||
openTab('initial')
|
||||
onDashboardReadyExecute()
|
||||
})
|
||||
});
|
||||
$('body')
|
||||
// .on('tab-away',function(){
|
||||
//
|
||||
// })
|
||||
// .on('tab-close',function(){
|
||||
//
|
||||
// })
|
||||
.on('click','.toggle-accordion-list',function(e){
|
||||
e.preventDefault();
|
||||
var el = $(this)
|
||||
var iconEl = el.find('i')
|
||||
var targetEl = el.parent().find('.accordion-list').first()
|
||||
targetEl.toggle()
|
||||
el.toggleClass('btn-primary btn-success')
|
||||
iconEl.toggleClass('fa-plus fa-minus')
|
||||
return false;
|
||||
})
|
||||
.on('click','.toggle-display-of-el',function(e){
|
||||
e.preventDefault();
|
||||
var el = $(this)
|
||||
var target = el.attr('data-target')
|
||||
var targetFirstOnly = el.attr('data-get') === 'first'
|
||||
var startIsParent = el.attr('data-start') === 'parent'
|
||||
var targetEl = startIsParent ? el.parent().find(target) : $(target)
|
||||
if(targetFirstOnly){
|
||||
targetEl = targetEl.first()
|
||||
}
|
||||
targetEl.toggle()
|
||||
return false;
|
||||
})
|
||||
.on('click','.pop-image',function(){
|
||||
var imageSrc = $(this).attr('src')
|
||||
if(!imageSrc){
|
||||
new PNotify({
|
||||
title: lang['Action Failed'],
|
||||
text: lang['No Image'],
|
||||
type: 'warning'
|
||||
})
|
||||
return;
|
||||
};
|
||||
popImage(imageSrc)
|
||||
})
|
||||
.on('click','.popped-image',function(){
|
||||
$(this).remove()
|
||||
})
|
||||
.on('click','.popped-image img',function(e){
|
||||
e.stopPropagation()
|
||||
return false;
|
||||
})
|
||||
.on('click','.go-home',goBackHome)
|
||||
.on('click','.go-back',goBackOneTab)
|
||||
.on('click','.delete-tab',function(e){
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var tabName = $(this).parents(`[page-open]`).attr(`page-open`)
|
||||
if(activeTabName === tabName){
|
||||
goBackOneTab()
|
||||
}
|
||||
deleteTab(tabName)
|
||||
return false;
|
||||
})
|
||||
.on('click','.delete-tab-dynamic',function(e){
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var tabName = $(this).parents('.page-tab').attr('id').replace('tab-','')
|
||||
goBackOneTab()
|
||||
deleteTab(tabName)
|
||||
return false;
|
||||
})
|
||||
.on('click','[page-open]',function(){
|
||||
var el = $(this)
|
||||
var pageChoice = el.attr('page-open')
|
||||
var pageOptions = JSON.parse(el.attr('page-options') || '{}')
|
||||
if(tabTree.name === pageChoice)return;
|
||||
openTab(pageChoice,pageOptions)
|
||||
})
|
||||
.on('click','[class_toggle]',function(){
|
||||
var el = $(this)
|
||||
var targetElement = el.attr('data-target')
|
||||
var classToToggle = el.attr('class_toggle')
|
||||
var iconClassesToToggle = el.attr('icon-toggle').split(' ')
|
||||
var iconTarget = el.attr('icon-child')
|
||||
var iconTargetElement = el.find(el.attr('icon-child'))
|
||||
var togglPosition = $(targetElement).hasClass(classToToggle) ? 0 : 1
|
||||
var classToggles = dashboardOptions().class_toggle || {}
|
||||
classToggles[targetElement] = [classToToggle,togglPosition,iconClassesToToggle,iconTarget];
|
||||
dashboardOptions('class_toggle',classToggles)
|
||||
$(targetElement).toggleClass(classToToggle)
|
||||
iconTargetElement
|
||||
.removeClass(iconClassesToToggle[togglPosition === 1 ? 0 : 1])
|
||||
.addClass(iconClassesToToggle[togglPosition])
|
||||
})
|
||||
.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();
|
||||
});
|
||||
})
|
||||
.on('click','[tab-chooser]',function(){
|
||||
var el = $(this)
|
||||
var parent = el.parents('[tab-chooser-parent]')
|
||||
var tabName = el.attr('tab-chooser')
|
||||
var allTabChoosersInParent = parent.find('[tab-chooser]')
|
||||
var allTabsInParent = parent.find('[tab-section]')
|
||||
allTabsInParent.hide()
|
||||
allTabChoosersInParent.removeClass('active')
|
||||
el.addClass('active')
|
||||
parent.find(`[tab-section="${tabName}"]`).show()
|
||||
});
|
||||
if(!isMobile){
|
||||
$('body').on('mousedown',"select[multiple]",function(e){
|
||||
e.preventDefault();
|
||||
var select = this;
|
||||
var scroll = select .scrollTop;
|
||||
e.target.selected = !e.target.selected;
|
||||
setTimeout(function(){select.scrollTop = scroll;}, 0);
|
||||
$(select).focus().change();
|
||||
}).on('mousemove',"select[multiple]",function(e){
|
||||
e.preventDefault()
|
||||
});
|
||||
}
|
||||
$('.logout').click(function(e){
|
||||
$.get(getApiPrefix() + '/logout/' + $user.ke + '/' + $user.uid,function(data){
|
||||
localStorage.removeItem('ShinobiLogin_'+location.host);
|
||||
location.href = location.href.split('#')[0];
|
||||
})
|
||||
})
|
||||
// only binded on load
|
||||
$('.form-section-header:not(.no-toggle-header)').click(function(e){
|
||||
var parent = $(this).parent('.form-group-group')
|
||||
var boxWrapper = parent.attr('id')
|
||||
parent.toggleClass('hide-box-wrapper')
|
||||
var hideBoxWrapper = parent.hasClass('hide-box-wrapper')
|
||||
boxWrappersHidden[boxWrapper] = hideBoxWrapper
|
||||
dashboardOptions('boxWrappersHidden',boxWrappersHidden)
|
||||
})
|
||||
$('[data-bs-target="#sidebarMenu"]').click(function(e){
|
||||
resizeMonitorIcons()
|
||||
})
|
||||
if(!isMobile){
|
||||
var clicked = false, clickX, oldClickX;
|
||||
var htmlBody = $('html')
|
||||
pageTabLinks.on({
|
||||
'mousemove': function(e) {
|
||||
clicked && updateScrollPos(e);
|
||||
},
|
||||
'mousedown': function(e) {
|
||||
e.preventDefault();
|
||||
clicked = true;
|
||||
oldClickX = clickX + 0;
|
||||
clickX = e.pageX;
|
||||
},
|
||||
'mouseup': function(e) {
|
||||
if(oldClickX !== clickX){
|
||||
e.preventDefault()
|
||||
}
|
||||
clicked = false;
|
||||
htmlBody.css('cursor', 'auto');
|
||||
}
|
||||
});
|
||||
|
||||
var updateScrollPos = function(e) {
|
||||
htmlBody.css('cursor', 'grabbing');
|
||||
pageTabLinks.scrollLeft(pageTabLinks.scrollLeft() + (clickX - e.pageX));
|
||||
}
|
||||
}
|
||||
|
||||
})
|
|
@ -239,7 +239,7 @@ function getApiHost(path,isAdmin){
|
|||
return getLocation() + (window.adminApiPrefix && isAdmin ? `${window.adminApiPrefix}` : '');
|
||||
}
|
||||
function getApiPrefix(path,isAdmin){
|
||||
var mainPart = getApiHost(path,isAdmin) + $user.auth_token
|
||||
var mainPart = getApiHost(path,isAdmin) + ($user.auth_token || $user.auth)
|
||||
return path ? mainPart + '/' + path + '/' + $user.ke : mainPart
|
||||
}
|
||||
|
||||
|
@ -1096,183 +1096,9 @@ function featureIsActivated(showNotice){
|
|||
return false
|
||||
}
|
||||
}
|
||||
$(document).ready(function(){
|
||||
onInitWebsocket(function(){
|
||||
loadMonitorsIntoMemory(function(data){
|
||||
setInterfaceCounts(data)
|
||||
openTab('initial')
|
||||
onDashboardReadyExecute()
|
||||
})
|
||||
});
|
||||
$('body')
|
||||
// .on('tab-away',function(){
|
||||
//
|
||||
// })
|
||||
// .on('tab-close',function(){
|
||||
//
|
||||
// })
|
||||
.on('click','.toggle-accordion-list',function(e){
|
||||
e.preventDefault();
|
||||
var el = $(this)
|
||||
var iconEl = el.find('i')
|
||||
var targetEl = el.parent().find('.accordion-list').first()
|
||||
targetEl.toggle()
|
||||
el.toggleClass('btn-primary btn-success')
|
||||
iconEl.toggleClass('fa-plus fa-minus')
|
||||
return false;
|
||||
})
|
||||
.on('click','.toggle-display-of-el',function(e){
|
||||
e.preventDefault();
|
||||
var el = $(this)
|
||||
var target = el.attr('data-target')
|
||||
var targetFirstOnly = el.attr('data-get') === 'first'
|
||||
var startIsParent = el.attr('data-start') === 'parent'
|
||||
var targetEl = startIsParent ? el.parent().find(target) : $(target)
|
||||
if(targetFirstOnly){
|
||||
targetEl = targetEl.first()
|
||||
}
|
||||
targetEl.toggle()
|
||||
return false;
|
||||
})
|
||||
.on('click','.pop-image',function(){
|
||||
var imageSrc = $(this).attr('src')
|
||||
if(!imageSrc){
|
||||
new PNotify({
|
||||
title: lang['Action Failed'],
|
||||
text: lang['No Image'],
|
||||
type: 'warning'
|
||||
})
|
||||
return;
|
||||
};
|
||||
popImage(imageSrc)
|
||||
})
|
||||
.on('click','.popped-image',function(){
|
||||
$(this).remove()
|
||||
})
|
||||
.on('click','.popped-image img',function(e){
|
||||
e.stopPropagation()
|
||||
return false;
|
||||
})
|
||||
.on('click','.go-home',goBackHome)
|
||||
.on('click','.go-back',goBackOneTab)
|
||||
.on('click','.delete-tab',function(e){
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var tabName = $(this).parents(`[page-open]`).attr(`page-open`)
|
||||
if(activeTabName === tabName){
|
||||
goBackOneTab()
|
||||
}
|
||||
deleteTab(tabName)
|
||||
return false;
|
||||
})
|
||||
.on('click','.delete-tab-dynamic',function(e){
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
var tabName = $(this).parents('.page-tab').attr('id').replace('tab-','')
|
||||
goBackOneTab()
|
||||
deleteTab(tabName)
|
||||
return false;
|
||||
})
|
||||
.on('click','[page-open]',function(){
|
||||
var el = $(this)
|
||||
var pageChoice = el.attr('page-open')
|
||||
var pageOptions = JSON.parse(el.attr('page-options') || '{}')
|
||||
if(tabTree.name === pageChoice)return;
|
||||
openTab(pageChoice,pageOptions)
|
||||
})
|
||||
.on('click','[class_toggle]',function(){
|
||||
var el = $(this)
|
||||
var targetElement = el.attr('data-target')
|
||||
var classToToggle = el.attr('class_toggle')
|
||||
var iconClassesToToggle = el.attr('icon-toggle').split(' ')
|
||||
var iconTarget = el.attr('icon-child')
|
||||
var iconTargetElement = el.find(el.attr('icon-child'))
|
||||
var togglPosition = $(targetElement).hasClass(classToToggle) ? 0 : 1
|
||||
var classToggles = dashboardOptions().class_toggle || {}
|
||||
classToggles[targetElement] = [classToToggle,togglPosition,iconClassesToToggle,iconTarget];
|
||||
dashboardOptions('class_toggle',classToggles)
|
||||
$(targetElement).toggleClass(classToToggle)
|
||||
iconTargetElement
|
||||
.removeClass(iconClassesToToggle[togglPosition === 1 ? 0 : 1])
|
||||
.addClass(iconClassesToToggle[togglPosition])
|
||||
})
|
||||
.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();
|
||||
});
|
||||
})
|
||||
.on('click','[tab-chooser]',function(){
|
||||
var el = $(this)
|
||||
var parent = el.parents('[tab-chooser-parent]')
|
||||
var tabName = el.attr('tab-chooser')
|
||||
var allTabChoosersInParent = parent.find('[tab-chooser]')
|
||||
var allTabsInParent = parent.find('[tab-section]')
|
||||
allTabsInParent.hide()
|
||||
allTabChoosersInParent.removeClass('active')
|
||||
el.addClass('active')
|
||||
parent.find(`[tab-section="${tabName}"]`).show()
|
||||
});
|
||||
if(!isMobile){
|
||||
$('body').on('mousedown',"select[multiple]",function(e){
|
||||
e.preventDefault();
|
||||
var select = this;
|
||||
var scroll = select .scrollTop;
|
||||
e.target.selected = !e.target.selected;
|
||||
setTimeout(function(){select.scrollTop = scroll;}, 0);
|
||||
$(select).focus().change();
|
||||
}).on('mousemove',"select[multiple]",function(e){
|
||||
e.preventDefault()
|
||||
});
|
||||
}
|
||||
$('.logout').click(function(e){
|
||||
$.get(getApiPrefix() + '/logout/' + $user.ke + '/' + $user.uid,function(data){
|
||||
localStorage.removeItem('ShinobiLogin_'+location.host);
|
||||
location.href = location.href.split('#')[0];
|
||||
})
|
||||
})
|
||||
// only binded on load
|
||||
$('.form-section-header:not(.no-toggle-header)').click(function(e){
|
||||
var parent = $(this).parent('.form-group-group')
|
||||
var boxWrapper = parent.attr('id')
|
||||
parent.toggleClass('hide-box-wrapper')
|
||||
var hideBoxWrapper = parent.hasClass('hide-box-wrapper')
|
||||
boxWrappersHidden[boxWrapper] = hideBoxWrapper
|
||||
dashboardOptions('boxWrappersHidden',boxWrappersHidden)
|
||||
})
|
||||
$('[data-bs-target="#sidebarMenu"]').click(function(e){
|
||||
resizeMonitorIcons()
|
||||
})
|
||||
if(!isMobile){
|
||||
var clicked = false, clickX, oldClickX;
|
||||
var htmlBody = $('html')
|
||||
pageTabLinks.on({
|
||||
'mousemove': function(e) {
|
||||
clicked && updateScrollPos(e);
|
||||
},
|
||||
'mousedown': function(e) {
|
||||
e.preventDefault();
|
||||
clicked = true;
|
||||
oldClickX = clickX + 0;
|
||||
clickX = e.pageX;
|
||||
},
|
||||
'mouseup': function(e) {
|
||||
if(oldClickX !== clickX){
|
||||
e.preventDefault()
|
||||
}
|
||||
clicked = false;
|
||||
htmlBody.css('cursor', 'auto');
|
||||
}
|
||||
});
|
||||
|
||||
var updateScrollPos = function(e) {
|
||||
htmlBody.css('cursor', 'grabbing');
|
||||
pageTabLinks.scrollLeft(pageTabLinks.scrollLeft() + (clickX - e.pageX));
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
function makeButton({ color, link, text, class: classes}){
|
||||
return `<a class="btn btn-sm d-block btn-${color} ${classes}" ${link ? `href="${link}" target="_blank"` : ''}>${text}</a>`
|
||||
}
|
||||
function replaceBrokenImage(_this){
|
||||
$(_this).attr('src', `${libURL}/libs/img/bg.jpg`)
|
||||
}
|
||||
|
|
|
@ -195,14 +195,39 @@ function initiateLiveGridPlayer(monitor,subStreamChannel){
|
|||
var stream = containerElement.find('.stream-element');
|
||||
var onPoseidonError = function(){
|
||||
// setTimeout(function(){
|
||||
// mainSocket.f({f:'monitor',ff:'watch_on',id:monitor.mid})
|
||||
// },5000)
|
||||
// mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId})
|
||||
// },2000)
|
||||
}
|
||||
if(!loadedPlayer.PoseidonErrorCount)loadedPlayer.PoseidonErrorCount = 0
|
||||
if(loadedPlayer.PoseidonErrorCount >= 5)return
|
||||
stream.attr('src',getApiPrefix(`mp4`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.mp4?time=' + (new Date()).getTime())
|
||||
stream[0].onerror = function(err){
|
||||
console.error(err)
|
||||
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)
|
||||
}
|
||||
}
|
||||
},1000)
|
||||
break;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
$(document).ready(function(){
|
||||
$('#settings').on('click','.ldap-sign-in',function(e){
|
||||
e.preventDefault()
|
||||
var signInWindow = window.open(getApiPrefix('loginTokenAddLDAP'),'popup','width=500,height=700,scrollbars=no,resizable=no');
|
||||
if(!signInWindow || signInWindow.closed || typeof signInWindow.closed=='undefined'){
|
||||
alert(`Your Popup Blocker is disabling this feature.`)
|
||||
}else{
|
||||
signInWindow.onbeforeunload = function(){
|
||||
drawAlternateLoginsToSettings()
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})
|
||||
})
|
|
@ -136,6 +136,7 @@ function resetMonitorCanvas(monitorId,initiateAfter,subStreamChannel){
|
|||
streamBlock.append(buildStreamElementHtml(streamType))
|
||||
attachVideoElementErrorHandler(monitorId)
|
||||
if(initiateAfter)initiateLiveGridPlayer(monitor,subStreamChannel)
|
||||
resetLiveGridDimensionsInMemory(monitorId)
|
||||
}
|
||||
function replaceMonitorInfoInHtml(htmlString,monitor){
|
||||
var monitorMutes = dashboardOptions().monitorMutes || {}
|
||||
|
@ -261,6 +262,7 @@ function loadVideoMiniList(monitorId){
|
|||
}
|
||||
function updateLiveGridElementHeightWidth(monitorId){
|
||||
var liveGridElement = liveGridElements[monitorId]
|
||||
liveGridElement.streamElement = liveGridElement.monitorItem.find('.stream-element')
|
||||
var streamElement = liveGridElement.streamElement
|
||||
liveGridElement.width = streamElement.width()
|
||||
liveGridElement.height = streamElement.height()
|
||||
|
@ -712,12 +714,19 @@ function openLiveGrid(){
|
|||
}
|
||||
}
|
||||
function popOutMonitor(monitorId){
|
||||
var monitorPop = monitorPops[monitorId]
|
||||
var monitorPop = monitorPops[monitorId] || {}
|
||||
if(monitorPop.isOpen){
|
||||
return
|
||||
}
|
||||
function finish(img){
|
||||
if(monitorPop){
|
||||
monitorPop.close()
|
||||
monitorPops[monitorId] = window.open(getApiPrefix() + '/embed/' + $user.ke + '/' + monitorId + '/fullscreen|jquery|relative|gui' + `?host=${location.pathname}`,'pop_' + monitorId + $user.auth_token,'height='+img.height+',width='+img.width);
|
||||
monitorPop = monitorPops[monitorId]
|
||||
monitorPop.isOpen = true
|
||||
monitorPop.onload = function(){
|
||||
this.onbeforeunload = function(){
|
||||
monitorPop.isOpen = false
|
||||
}
|
||||
}
|
||||
monitorPop = window.open(getApiPrefix() + '/embed/' + $user.ke + '/' + monitorId + '/fullscreen|jquery|relative|gui' + `?host=${location.pathname}`,'pop_' + monitorId + $user.auth_token,'height='+img.height+',width='+img.width);
|
||||
}
|
||||
if(loadedLiveGrids[monitorId]){
|
||||
getSnapshot(loadedMonitors[monitorId],function(url){
|
||||
|
@ -736,6 +745,12 @@ function popOutMonitor(monitorId){
|
|||
finish(img)
|
||||
}
|
||||
}
|
||||
function createWallViewWindow(windowName){
|
||||
var el = $(document)
|
||||
var width = el.width()
|
||||
var height = el.height()
|
||||
window.open(getApiPrefix() + '/wallview/' + $user.ke + (windowName ? 'window=' + windowName : ''), 'wallview_'+windowName, 'height='+height+',width='+width)
|
||||
}
|
||||
function fullScreenLiveGridStream(monitorItem){
|
||||
var videoElement = monitorItem.find('.stream-element')
|
||||
monitorItem.addClass('fullscreen')
|
||||
|
@ -1092,6 +1107,9 @@ $(document).ready(function(e){
|
|||
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
||||
popOutMonitor(monitorId)
|
||||
})
|
||||
.on('click','.open-wallview',function(){
|
||||
createWallViewWindow()
|
||||
})
|
||||
.on('click','.toggle-monitor-substream',function(){
|
||||
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
||||
toggleSubStream(monitorId)
|
||||
|
@ -1290,6 +1308,7 @@ $(document).ready(function(e){
|
|||
// break;
|
||||
case'detector_trigger':
|
||||
var monitorId = d.id
|
||||
var matrices = d.details.matrices
|
||||
var liveGridElement = liveGridElements[monitorId]
|
||||
if(!window.dontShowDetection && liveGridElement){
|
||||
var monitorElement = liveGridElement.monitorItem
|
||||
|
@ -1303,12 +1322,12 @@ $(document).ready(function(e){
|
|||
}else{
|
||||
monitorElement.removeClass('doObjectDetection')
|
||||
}
|
||||
if(d.details.matrices&&d.details.matrices.length>0){
|
||||
if(matrices && matrices.length > 0){
|
||||
drawMatrices(d,{
|
||||
theContainer: liveGridElement.eventObjects,
|
||||
height: liveGridElement.height,
|
||||
width: liveGridElement.width,
|
||||
})
|
||||
}, null, true)
|
||||
}
|
||||
if(d.details.confidence){
|
||||
var eventConfidence = d.details.confidence
|
||||
|
@ -1329,7 +1348,7 @@ $(document).ready(function(e){
|
|||
}
|
||||
playAudioAlert()
|
||||
var monitorPop = monitorPops[monitorId]
|
||||
if(window.popLiveOnEvent && (!monitorPop || monitorPop.closed === true)){
|
||||
if(window.popLiveOnEvent && (!monitorPop || !monitorPop.isOpen)){
|
||||
popOutMonitor(monitorId)
|
||||
}
|
||||
// console.log({
|
||||
|
|
|
@ -1150,8 +1150,8 @@ editorForm.find('[name="type"]').change(function(e){
|
|||
setCosmeticMonitorInfo(newMonitorData)
|
||||
drawMonitorGroupList()
|
||||
if(!d.silenceNote){
|
||||
new PNotify({
|
||||
title: 'Monitor Saved',
|
||||
redAlertNotify({
|
||||
title: lang['Monitor Saved'],
|
||||
text: '<b>'+newMonitorData.name+'</b> <small>'+newMonitorData.mid+'</small> has been saved.',
|
||||
type: 'success'
|
||||
})
|
||||
|
|
|
@ -669,6 +669,25 @@ 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
|
||||
};
|
||||
if (redAlertNotice) {
|
||||
redAlertNotice.update(notifyOptions);
|
||||
} else {
|
||||
redAlertNotices[title] = new PNotify(notifyOptions);
|
||||
redAlertNotices[title].on('close', function() {
|
||||
redAlertNotices[title] = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
function buildPosePoints(bodyParts, x, y){
|
||||
let theArray = []
|
||||
for(const point of bodyParts){
|
||||
|
@ -683,7 +702,7 @@ function buildPosePoints(bodyParts, x, y){
|
|||
}
|
||||
return theArray;
|
||||
}
|
||||
function drawMatrices(event,options){
|
||||
function drawMatrices(event, options, autoRemoveTimeout, drawTrails){
|
||||
var theContainer = options.theContainer
|
||||
var height = options.height
|
||||
var width = options.width
|
||||
|
@ -695,8 +714,11 @@ function drawMatrices(event,options){
|
|||
let moreMatrices = []
|
||||
var monitorId = event.id;
|
||||
function processMatrix(n,matrix){
|
||||
html += `<div class="stream-detected-object" name="${objectTagGroup}" style="height:${heightRatio * matrix.height}px;width:${widthRatio * matrix.width}px;top:${heightRatio * matrix.y}px;left:${widthRatio * matrix.x}px;border-color: ${matrix.color};">`
|
||||
if(matrix.tag)html += `<span class="tag">${matrix.tag}${!isNaN(matrix.id) ? ` <small class="label label-default">${matrix.id}</small>`: ''}</span>`
|
||||
const newWidth = widthRatio * matrix.width;
|
||||
const newHeight = heightRatio * matrix.height;
|
||||
if(drawTrails)html += `<div class="stream-detected-object fresh-detected-trail" style="height:2px;width:2px;top:${heightRatio * matrix.y + (newHeight / 2)}px;left:${widthRatio * matrix.x + (newWidth / 2)}px;border-color: green;"></div>`
|
||||
html += `<div class="stream-detected-object fresh-detected-object" name="${objectTagGroup}" style="height:${newHeight}px;width:${newWidth}px;top:${heightRatio * matrix.y}px;left:${widthRatio * matrix.x}px;border-color: ${matrix.color};">`
|
||||
if(matrix.tag)html += `<span class="tag">${matrix.tag}${!isNaN(matrix.id) ? ` <small class="label label-default">${matrix.id}</small>`: ''} (${matrix.confidence.toFixed(2) || 0})</span>`
|
||||
if(matrix.notice)html += `<div class="matrix-info" style="color:yellow">${matrix.notice}</div>`;
|
||||
if(matrix.missingNear && matrix.missingNear.length > 0){
|
||||
html += `<div class="matrix-info yellow"><small>Missing Near</small><br>${matrix.missingRecently.map(item => `${item.tag} (${item.id}) by ${item.missedNear.tag} (${item.missedNear.id})`).join(', ')}</div>`;
|
||||
|
@ -728,7 +750,7 @@ function drawMatrices(event,options){
|
|||
if(matrix.nearBy){
|
||||
html += `<div class="matrix-info">`
|
||||
matrix.nearBy.forEach((nearMatrix) => {
|
||||
html += `<div class="mb-1">${nearMatrix.tag} <small class="label label-default">${nearMatrix.id}</small> (${nearMatrix.overlapPercent}%)</div>`
|
||||
html += `<div class="mb-1">${nearMatrix.tag} <small class="label label-default">${nearMatrix.id}</small> (${nearMatrix.overlapPercent.toFixed(2)}%)</div>`
|
||||
});
|
||||
html += `</div>`
|
||||
}
|
||||
|
@ -740,7 +762,19 @@ function drawMatrices(event,options){
|
|||
}
|
||||
$.each(event.details.matrices, processMatrix);
|
||||
$.each(moreMatrices, processMatrix);
|
||||
theContainer.append(html)
|
||||
var addedEls = theContainer.append(html)
|
||||
if(autoRemoveTimeout){
|
||||
addedEls = addedEls.find('.fresh-detected-object').removeClass('fresh-detected-object')
|
||||
setTimeout(function(){
|
||||
addedEls.remove()
|
||||
}, autoRemoveTimeout);
|
||||
}
|
||||
if(drawTrails){
|
||||
var addedTrails = theContainer.find('.fresh-detected-trail').removeClass('fresh-detected-trail')
|
||||
setTimeout(function(){
|
||||
addedTrails.remove()
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
function setMonitorCountOnUI(){
|
||||
$('.cameraCount').text(Object.keys(loadedMonitors).length)
|
||||
|
|
|
@ -4,6 +4,8 @@ $(document).ready(function(e){
|
|||
var loadedResultsByIp = {}
|
||||
var monitorEditorWindow = $('#tab-monitorSettings')
|
||||
var onvifScannerWindow = $('#tab-onvifScanner')
|
||||
var onvifScannerStartButton = onvifScannerWindow.find('.start-scan')
|
||||
var onvifScannerStopButton = onvifScannerWindow.find('.stop-scan')
|
||||
var onvifScannerResultPane = onvifScannerWindow.find('.onvif_result')
|
||||
var onvifScannerErrorResultPane = onvifScannerWindow.find('.onvif_result_error')
|
||||
var scanForm = onvifScannerWindow.find('form');
|
||||
|
@ -30,86 +32,97 @@ $(document).ready(function(e){
|
|||
var html = buildSubMenuItems(allFound)
|
||||
sideMenuList.html(html)
|
||||
}
|
||||
var setAsLoading = function(appearance){
|
||||
var showStopButton = function(appearance){
|
||||
if(appearance){
|
||||
onvifScannerWindow.find('._loading').show()
|
||||
onvifScannerWindow.find('[type="submit"]').prop('disabled',true)
|
||||
onvifScannerStartButton.addClass('d-none')
|
||||
onvifScannerStopButton.removeClass('d-none')
|
||||
}else{
|
||||
onvifScannerWindow.find('._loading').hide()
|
||||
onvifScannerWindow.find('[type="submit"]').prop('disabled',false)
|
||||
onvifScannerStartButton.removeClass('d-none')
|
||||
onvifScannerStopButton.addClass('d-none')
|
||||
}
|
||||
}
|
||||
function drawProbeResult(options){
|
||||
if(!options.error){
|
||||
var currentUsername = onvifScannerWindow.find('[name="user"]').val()
|
||||
var currentPassword = onvifScannerWindow.find('[name="pass"]').val()
|
||||
var tempID = generateId()
|
||||
var info = options.info ? jsonToHtmlBlock(options.info) : ''
|
||||
var streamUrl = ''
|
||||
var launchWebPage = `target="_blank" href="http${options.port == 443 ? 's' : ''}://${options.ip}:${options.port}"`
|
||||
if(options.uri){
|
||||
streamUrl = options.uri
|
||||
}
|
||||
var theLocation = getLocationFromUri(options.uri)
|
||||
var pathLocation = theLocation.location
|
||||
var monitorConfigPartial = {
|
||||
name: pathLocation.hostname,
|
||||
mid: tempID + `${options.port}`,
|
||||
host: pathLocation.hostname,
|
||||
port: pathLocation.port,
|
||||
path: pathLocation.pathname + (pathLocation.search && pathLocation.search !== '?' ? pathLocation.search : ''),
|
||||
protocol: theLocation.protocol,
|
||||
details: {
|
||||
auto_host: addCredentialsToUri(streamUrl,currentUsername,currentPassword),
|
||||
muser: currentUsername,
|
||||
mpass: currentPassword,
|
||||
is_onvif: '1',
|
||||
onvif_port: options.port,
|
||||
},
|
||||
}
|
||||
if(options.isPTZ){
|
||||
monitorConfigPartial.details = Object.assign(monitorConfigPartial.details,{
|
||||
control: '1',
|
||||
control_url_method: 'ONVIF',
|
||||
control_stop: '1',
|
||||
})
|
||||
}
|
||||
var monitorAlreadyAdded = isOnvifRowAlreadyALoadedMonitor(monitorConfigPartial)
|
||||
if(monitorAlreadyAdded){
|
||||
monitorConfigPartial.mid = monitorAlreadyAdded.mid;
|
||||
}
|
||||
var monitorId = monitorConfigPartial.mid
|
||||
loadedResults[monitorId] = monitorConfigPartial;
|
||||
loadedResultsByIp[monitorConfigPartial.host] = monitorConfigPartial;
|
||||
onvifScannerResultPane.append(`
|
||||
<div class="col-md-4 mb-3" onvif_row="${monitorId}" id="onvif-result-${monitorId}">
|
||||
<div style="display:block" class="card shadow btn-default copy">
|
||||
<div class="preview-image card-header" style="background-image:url(${options.snapShot ? 'data:image/png;base64,' + options.snapShot : placeholder.getData(placeholder.plcimg({text: ' ', fsize: 25, bgcolor:'#1f80f9'}))})"></div>
|
||||
<div class="card-body" style="min-height:190px">
|
||||
<div>${info}</div>
|
||||
<div class="url">${streamUrl}</div>
|
||||
</div>
|
||||
<div class="card-footer">${options.ip}:${options.port}</div>
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
onvifScannerWindow.find('._notfound').remove()
|
||||
setAsLoading(false)
|
||||
drawFoundCamerasSubMenu()
|
||||
|
||||
function drawDeviceTableRow(device, gotAccess){
|
||||
var ip = device.ip;
|
||||
var el = onvifScannerResultPane.find(`[scan-item="${ip}"]`)
|
||||
var hasError = !!device.error;
|
||||
var uriText = !hasError ? device.uri ? device.uri.split('?')[0] : '' : device.error;
|
||||
var statusColor = hasError ? 'red' : 'green';
|
||||
var snapShot = device.snapShot;
|
||||
// console.log(ip, device.error, hasError)
|
||||
if(gotAccess)loadMonitorConfigFromResult(device)
|
||||
if(el.length === 0){
|
||||
var html = `<div scan-item="${ip}" class="col-md-6">
|
||||
<div class="card btn-default d-flex flex-row align-items-center p-2 mb-3 mx-2" style="border:none;border-left: 3px solid;border-color: ${statusColor}">
|
||||
<div class="pr-2"><i class="fa fa-square" style="color:${statusColor}"></i></div>
|
||||
<div class="pr-2"><div class="scan-item-img copy ${snapShot ? `cursor-pointer` : ''}" style="${snapShot ? `background-image:url(data:image/jpeg;base64,${snapShot})` : 'background-color:${statusColor};'}"></div></div>
|
||||
<div class="pr-2 flex-grow-1">${ip}<br><small class="uri">${uriText}</small></div>
|
||||
<div class="text-center copy-button pr-2">${!hasError ? makeButton({text: lang.Copy, class:'copy', color: 'primary'}) : ''}</div>
|
||||
</div>
|
||||
</div>`
|
||||
onvifScannerResultPane.append(html)
|
||||
}else{
|
||||
if(!loadedResultsByIp[options.ip]){
|
||||
onvifScannerErrorResultPane.append(`
|
||||
<div onvif_error_row="${options.ip}" class="d-flex flex-row">
|
||||
<div class="py-2 px-1" style="min-width:170px"><b>${options.ip}:${options.port}</b></div>
|
||||
<div class="py-2 px-1 flex-grow-1">${options.error}</div>
|
||||
<div class="py-2 px-1 text-right">
|
||||
<a target="_blank" class="btn btn-sm btn-secondary" href="http://${options.ip}:${options.port}"><i class="fa fa-external-link"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
var copyButton = el.find('.copy-button');
|
||||
var imgEl = el.find('.scan-item-img');
|
||||
if(hasError){
|
||||
copyButton.empty()
|
||||
imgEl.removeClass('copy cursor-pointer')
|
||||
}else{
|
||||
copyButton.html(makeButton({text: lang.Copy, class:'copy', color: 'primary'}))
|
||||
imgEl.addClass('copy cursor-pointer')
|
||||
}
|
||||
if(snapShot){
|
||||
imgEl.css('background-image', `url("data:image/jpeg;base64,${snapShot}")`)
|
||||
}else{
|
||||
imgEl.css('background-image', '')
|
||||
}
|
||||
imgEl.css('background-color', statusColor)
|
||||
el.find('.uri').text(uriText)
|
||||
el.find('.card').css('border-color', statusColor)
|
||||
el.find('.fa-circle').css('color', statusColor)
|
||||
}
|
||||
}
|
||||
function loadMonitorConfigFromResult(options){
|
||||
var monitorId = removeSpecialCharacters(options.ip)
|
||||
var currentUsername = options.user
|
||||
var currentPassword = options.pass
|
||||
var streamUrl = ''
|
||||
var launchWebPage = `target="_blank" href="http${options.port == 443 ? 's' : ''}://${options.ip}:${options.port}"`
|
||||
if(options.uri){
|
||||
streamUrl = options.uri
|
||||
}
|
||||
var theLocation = getLocationFromUri(options.uri)
|
||||
var pathLocation = theLocation.location
|
||||
var monitorConfigPartial = {
|
||||
name: pathLocation.hostname,
|
||||
mid: monitorId,
|
||||
host: pathLocation.hostname,
|
||||
port: pathLocation.port,
|
||||
path: pathLocation.pathname + (pathLocation.search && pathLocation.search !== '?' ? pathLocation.search : ''),
|
||||
protocol: theLocation.protocol,
|
||||
details: {
|
||||
auto_host: addCredentialsToUri(streamUrl,currentUsername,currentPassword),
|
||||
muser: currentUsername,
|
||||
mpass: currentPassword,
|
||||
is_onvif: '1',
|
||||
onvif_port: options.port,
|
||||
},
|
||||
}
|
||||
if(options.isPTZ){
|
||||
monitorConfigPartial.details = Object.assign(monitorConfigPartial.details,{
|
||||
control: '1',
|
||||
control_url_method: 'ONVIF',
|
||||
control_stop: '1',
|
||||
})
|
||||
}
|
||||
var monitorAlreadyAdded = isOnvifRowAlreadyALoadedMonitor(monitorConfigPartial)
|
||||
if(monitorAlreadyAdded){
|
||||
monitorConfigPartial.mid = monitorAlreadyAdded.mid;
|
||||
}
|
||||
loadedResults[monitorId] = monitorConfigPartial;
|
||||
loadedResultsByIp[monitorConfigPartial.host] = monitorConfigPartial;
|
||||
return monitorConfigPartial
|
||||
}
|
||||
function isOnvifRowAlreadyALoadedMonitor(onvifRow){
|
||||
var matches = null;
|
||||
$.each(loadedMonitors,function(n,monitor){
|
||||
|
@ -174,7 +187,7 @@ $(document).ready(function(e){
|
|||
var form = el.serializeObject();
|
||||
onvifScannerResultPane.empty();
|
||||
onvifScannerErrorResultPane.empty();
|
||||
setAsLoading(true)
|
||||
showStopButton(true)
|
||||
mainSocket.f({
|
||||
f: 'onvif',
|
||||
ip: form.ip,
|
||||
|
@ -184,34 +197,80 @@ $(document).ready(function(e){
|
|||
});
|
||||
clearTimeout(checkTimeout)
|
||||
checkTimeout = setTimeout(function(){
|
||||
if(onvifScannerResultPane.find('.card').length === 0){
|
||||
setAsLoading(false)
|
||||
if(onvifScannerResultPane.find('[scan-item]').length === 0){
|
||||
showStopButton(false)
|
||||
onvifScannerResultPane.append(`<div class="p-2 text-center ${definitions.Theme.isDark ? 'text-white' : ''} _notfound text-white epic-text">${lang.sorryNothingWasFound}</div>`)
|
||||
}
|
||||
},5000)
|
||||
return false;
|
||||
});
|
||||
onvifScannerWindow.on('click','.copy',function(){
|
||||
onvifScannerWindow.on('click','.copy',function(e){
|
||||
e.preventDefault()
|
||||
openMonitorEditorPage()
|
||||
var el = $(this).parents('[onvif_row]');
|
||||
var id = el.attr('onvif_row');
|
||||
var onvifRecord = loadedResults[id];
|
||||
var el = $(this).parents('[scan-item]');
|
||||
var id = el.attr('scan-item');
|
||||
var onvifRecord = loadedResultsByIp[id];
|
||||
var streamURL = onvifRecord.details.auto_host
|
||||
writeToMonitorSettingsWindow(onvifRecord)
|
||||
})
|
||||
onvifScannerWindow.on('click','.add-all',function(){
|
||||
filterOutMonitorsThatAreAlreadyAdded(loadedResults,function(importableCameras){
|
||||
$.each(importableCameras,function(n,camera){
|
||||
// console.log(camera)
|
||||
postMonitor(camera)
|
||||
})
|
||||
const numberOfCameras = importableCameras.length
|
||||
if(numberOfCameras === 0){
|
||||
new PNotify({
|
||||
title: lang["ONVIF Scanner"],
|
||||
text: lang.sorryNothingWasFound,
|
||||
type: 'danger',
|
||||
})
|
||||
}else{
|
||||
$.confirm.create({
|
||||
title: lang['Add Cameras'],
|
||||
body: `<p>${lang.addAllCamerasText.replace('9001', numberOfCameras)}</p><ul>${importableCameras.map(item => `<li>${item.host}</li>`).join('')}</ul>`,
|
||||
clickOptions: {
|
||||
class: 'btn-success',
|
||||
title: lang.Add,
|
||||
},
|
||||
clickCallback: function(){
|
||||
$.each(importableCameras,function(n,camera){
|
||||
// console.log(camera)
|
||||
postMonitor(camera)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
onvifScannerWindow.on('click','.stop-scan',function(){
|
||||
mainSocket.f({ f: 'onvif_stop' });
|
||||
})
|
||||
|
||||
loadLocalOptions()
|
||||
onInitWebsocket(function (){
|
||||
mainSocket.f({ f: 'onvif_scan_reconnect' });
|
||||
})
|
||||
onWebSocketEvent(function (d){
|
||||
switch(d.f){
|
||||
case'onvif':
|
||||
drawProbeResult(d)
|
||||
try{
|
||||
drawDeviceTableRow(d, d.ff !== 'failed_capture' && !d.failedConnection);
|
||||
}catch(err){
|
||||
console.error(err)
|
||||
}
|
||||
break;
|
||||
case'onvif_scan_current':
|
||||
console.log(d)
|
||||
if(d.isScanning){
|
||||
showStopButton(true)
|
||||
}else{
|
||||
showStopButton(false)
|
||||
}
|
||||
d.devices.forEach(device => {
|
||||
console.log('onvif_scan_current', device)
|
||||
drawDeviceTableRow(device, !device.error && !d.failedConnection)
|
||||
});
|
||||
break;
|
||||
case'onvif_scan_complete':
|
||||
showStopButton(false)
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
|
|
@ -8,6 +8,7 @@ $(document).ready(function(){
|
|||
var timeStripObjectSearchInput = $('#timeline-video-object-search');
|
||||
var dateSelector = $('#timeline-date-selector');
|
||||
var sideMenuList = $(`#side-menu-link-timeline ul`)
|
||||
var monitorList = $(`#timeline-monitor-list`)
|
||||
var playToggles = timeStripControls.find('[timeline-action="playpause"]')
|
||||
var speedButtons = timeStripControls.find('[timeline-action="speed"]')
|
||||
var gridSizeButtons = timeStripControls.find('[timeline-action="gridSize"]')
|
||||
|
@ -50,6 +51,8 @@ $(document).ready(function(){
|
|||
var dateRangeChanging = false
|
||||
var lastDateChecked = new Date(0)
|
||||
var monitorSelectionElements = []
|
||||
var sideMenuListMissing = sideMenuList.length === 0;
|
||||
var selectingMonitorList = (sideMenuListMissing ? monitorList : sideMenuList);
|
||||
function setLoadingMask(turnOn){
|
||||
if(turnOn){
|
||||
if(theWindow.find('.loading-mask').length === 0){
|
||||
|
@ -441,7 +444,7 @@ $(document).ready(function(){
|
|||
function setVideoInCanvas(newVideo){
|
||||
var monitorId = newVideo.mid
|
||||
var container = getVideoContainerInCanvas(newVideo)
|
||||
.removeClass('no-video').find('.film').html(`<video muted src="${newVideo.href}"></video>`)
|
||||
.removeClass('no-video').find('.film').html(`<video muted src="${getApiPrefix('videos')}/${monitorId}/${newVideo.filename}"></video>`)
|
||||
var vidEl = getVideoElInCanvas(newVideo)
|
||||
var objectContainer = getObjectContainerInCanvas(newVideo)
|
||||
vidEl.playbackRate = timelineSpeed
|
||||
|
@ -821,15 +824,16 @@ $(document).ready(function(){
|
|||
})
|
||||
})
|
||||
var html = buildSubMenuItems(allFound)
|
||||
sideMenuList.html(html)
|
||||
monitorSelectionElements = sideMenuList.find('.timeline-selectMonitor')
|
||||
if(!sideMenuListMissing)sideMenuList.html(html)
|
||||
monitorList.html(html)
|
||||
monitorSelectionElements = selectingMonitorList.find('.timeline-selectMonitor')
|
||||
}
|
||||
async function setSideMenuMonitorVisualSelection(){
|
||||
var getForAllMonitors = timeStripSelectedMonitors.length === 0;
|
||||
monitorSelectionElements.find('.dot').removeClass('dot-green')
|
||||
if(!getForAllMonitors){
|
||||
timeStripSelectedMonitors.forEach((monitorId) => {
|
||||
sideMenuList.find(`[data-mid="${monitorId}"] .dot`).addClass('dot-green')
|
||||
selectingMonitorList.find(`[data-mid="${monitorId}"] .dot`).addClass('dot-green')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -928,7 +932,7 @@ $(document).ready(function(){
|
|||
refreshTimeline()
|
||||
}
|
||||
}
|
||||
sideMenuList.on('click','[timeline-menu-action]',function(){
|
||||
function monitorSelectorController(){
|
||||
var el = $(this)
|
||||
var type = el.attr('timeline-menu-action')
|
||||
switch(type){
|
||||
|
@ -957,7 +961,9 @@ $(document).ready(function(){
|
|||
}
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
monitorList.on('click','[timeline-menu-action]', monitorSelectorController)
|
||||
sideMenuList.on('click','[timeline-menu-action]', monitorSelectorController)
|
||||
timelineActionButtons.click(function(){
|
||||
var el = $(this)
|
||||
var type = el.attr('timeline-action')
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
onWebSocketEvent(function(d){
|
||||
switch(d.f){
|
||||
case'video_edit':case'video_archive':
|
||||
var video = loadedVideosInMemory[`${d.mid}${d.time}${d.type}`]
|
||||
if(video){
|
||||
let filename = `${formattedTimeForFilename(convertTZ(d.time),false,`YYYY-MM-DDTHH-mm-ss`)}.${video.ext || 'mp4'}`
|
||||
loadedVideosInMemory[`${d.mid}${d.time}${d.type}`].status = d.status
|
||||
$(`[data-mid="${d.mid}"][data-filename="${filename}"]`).attr('data-status',d.status);
|
||||
}
|
||||
break;
|
||||
case'video_delete':
|
||||
$('[file="'+d.filename+'"][mid="'+d.mid+'"]:not(.modal)').remove();
|
||||
$('[data-file="'+d.filename+'"][data-mid="'+d.mid+'"]:not(.modal)').remove();
|
||||
$('[data-time-formed="'+(new Date(d.time))+'"][data-mid="'+d.mid+'"]:not(.modal)').remove();
|
||||
var videoPlayerId = getVideoPlayerTabId(d)
|
||||
if(tabTree.name === videoPlayerId){
|
||||
goBackOneTab()
|
||||
}
|
||||
deleteTab(videoPlayerId)
|
||||
break;
|
||||
}
|
||||
})
|
||||
$(document).ready(function(){
|
||||
$('body')
|
||||
.on('click','.open-video',function(e){
|
||||
e.preventDefault()
|
||||
var _this = this;
|
||||
var {
|
||||
monitorId,
|
||||
videoTime,
|
||||
video,
|
||||
} = getVideoInfoFromEl(_this)
|
||||
createVideoPlayerTab(video)
|
||||
setVideoStatus(video)
|
||||
return false;
|
||||
})
|
||||
.on('click','[video-time-seeked-video-position]',function(){
|
||||
var el = $(this)
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('video-time-seeked-video-position')
|
||||
var timeInward = (parseInt(el.attr('video-slice-seeked')) / 1000) - 2
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}${undefined}`]
|
||||
timeInward = timeInward < 0 ? 0 : timeInward
|
||||
createVideoPlayerTab(video,timeInward)
|
||||
})
|
||||
.on('click','.delete-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
var type = el.attr('data-type')
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}${type}`]
|
||||
var videoSet = video.videoSet
|
||||
var ext = video.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var isCloudVideo = videoSet === 'cloudVideos'
|
||||
var videoEndpoint = getApiPrefix(videoSet || 'videos') + '/' + video.mid + '/' + video.filename
|
||||
var endpointType = isCloudVideo ? `?type=${video.type}` : ''
|
||||
$.confirm.create({
|
||||
title: lang["Delete Video"] + ' : ' + video.filename,
|
||||
body: `${lang.DeleteVideoMsg}<br><br><div class="row"><video class="video_video" autoplay loop controls><source src="${videoEndpoint}${endpointType}" type="video/${ext}"></video></div>`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-trash-o"></i> ' + lang.Delete,
|
||||
class: 'btn-danger btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
$.getJSON(videoEndpoint + '/delete' + endpointType,function(data){
|
||||
if(data.ok){
|
||||
console.log('Video Deleted')
|
||||
}else{
|
||||
console.log('Video Not Deleted',data,videoEndpoint + endpointType)
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return false;
|
||||
})
|
||||
.on('click','.compress-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}${undefined}`]
|
||||
var ext = video.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
$.confirm.create({
|
||||
title: lang["Compress"] + ' : ' + video.filename,
|
||||
body: `${lang.CompressVideoMsg}<br><br><div class="row"><video class="video_video" autoplay loop controls><source src="${videoEndpoint}" type="video/${ext}"></video></div>`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-compress"></i> ' + lang.Compress,
|
||||
class: 'btn-primary btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
compressVideo(video)
|
||||
}
|
||||
});
|
||||
return false;
|
||||
})
|
||||
.on('click','.archive-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
var unarchive = $(this).hasClass('status-archived')
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}${undefined}`]
|
||||
var ext = video.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
if(unarchive){
|
||||
unarchiveVideo(video)
|
||||
}else{
|
||||
// $.confirm.create({
|
||||
// title: lang["Archive"] + ' : ' + video.filename,
|
||||
// body: `${lang.ArchiveVideoMsg}<br><br><div class="row"><video class="video_video" autoplay loop controls><source src="${videoEndpoint}" type="video/${ext}"></video></div>`,
|
||||
// clickOptions: {
|
||||
// title: '<i class="fa fa-lock"></i> ' + lang.Archive,
|
||||
// class: 'btn-primary btn-sm'
|
||||
// },
|
||||
// clickCallback: function(){
|
||||
archiveVideo(video)
|
||||
// }
|
||||
// });
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.on('click','.fix-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}${undefined}`]
|
||||
var ext = video.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
$.confirm.create({
|
||||
title: lang["Fix Video"] + ' : ' + video.filename,
|
||||
body: `${lang.FixVideoMsg}<br><br><div class="row"><video class="video_video" autoplay loop controls><source src="${videoEndpoint}" type="video/${ext}"></video></div>`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-wrench"></i> ' + lang.Fix,
|
||||
class: 'btn-danger btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
$.getJSON(videoEndpoint + '/fix',function(data){
|
||||
if(data.ok){
|
||||
console.log('Video Fixed')
|
||||
}else{
|
||||
console.log('Video Not Fixed',data,videoEndpoint)
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return false;
|
||||
})
|
||||
})
|
|
@ -658,158 +658,3 @@ function getDisplayDimensions(videoElement) {
|
|||
videoHeight: displayHeight,
|
||||
};
|
||||
}
|
||||
onWebSocketEvent(function(d){
|
||||
switch(d.f){
|
||||
case'video_edit':case'video_archive':
|
||||
var video = loadedVideosInMemory[`${d.mid}${d.time}${d.type}`]
|
||||
if(video){
|
||||
let filename = `${formattedTimeForFilename(convertTZ(d.time),false,`YYYY-MM-DDTHH-mm-ss`)}.${video.ext || 'mp4'}`
|
||||
loadedVideosInMemory[`${d.mid}${d.time}${d.type}`].status = d.status
|
||||
$(`[data-mid="${d.mid}"][data-filename="${filename}"]`).attr('data-status',d.status);
|
||||
}
|
||||
break;
|
||||
case'video_delete':
|
||||
$('[file="'+d.filename+'"][mid="'+d.mid+'"]:not(.modal)').remove();
|
||||
$('[data-file="'+d.filename+'"][data-mid="'+d.mid+'"]:not(.modal)').remove();
|
||||
$('[data-time-formed="'+(new Date(d.time))+'"][data-mid="'+d.mid+'"]:not(.modal)').remove();
|
||||
var videoPlayerId = getVideoPlayerTabId(d)
|
||||
if(tabTree.name === videoPlayerId){
|
||||
goBackOneTab()
|
||||
}
|
||||
deleteTab(videoPlayerId)
|
||||
break;
|
||||
}
|
||||
})
|
||||
$(document).ready(function(){
|
||||
$('body')
|
||||
.on('click','.open-video',function(e){
|
||||
e.preventDefault()
|
||||
var _this = this;
|
||||
var {
|
||||
monitorId,
|
||||
videoTime,
|
||||
video,
|
||||
} = getVideoInfoFromEl(_this)
|
||||
createVideoPlayerTab(video)
|
||||
setVideoStatus(video)
|
||||
return false;
|
||||
})
|
||||
.on('click','[video-time-seeked-video-position]',function(){
|
||||
var el = $(this)
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('video-time-seeked-video-position')
|
||||
var timeInward = (parseInt(el.attr('video-slice-seeked')) / 1000) - 2
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}${undefined}`]
|
||||
timeInward = timeInward < 0 ? 0 : timeInward
|
||||
createVideoPlayerTab(video,timeInward)
|
||||
})
|
||||
.on('click','.delete-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
var type = el.attr('data-type')
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}${type}`]
|
||||
var videoSet = video.videoSet
|
||||
var ext = video.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var isCloudVideo = videoSet === 'cloudVideos'
|
||||
var videoEndpoint = getApiPrefix(videoSet || 'videos') + '/' + video.mid + '/' + video.filename
|
||||
var endpointType = isCloudVideo ? `?type=${video.type}` : ''
|
||||
$.confirm.create({
|
||||
title: lang["Delete Video"] + ' : ' + video.filename,
|
||||
body: `${lang.DeleteVideoMsg}<br><br><div class="row"><video class="video_video" autoplay loop controls><source src="${videoEndpoint}${endpointType}" type="video/${ext}"></video></div>`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-trash-o"></i> ' + lang.Delete,
|
||||
class: 'btn-danger btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
$.getJSON(videoEndpoint + '/delete' + endpointType,function(data){
|
||||
if(data.ok){
|
||||
console.log('Video Deleted')
|
||||
}else{
|
||||
console.log('Video Not Deleted',data,videoEndpoint + endpointType)
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return false;
|
||||
})
|
||||
.on('click','.compress-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}${undefined}`]
|
||||
var ext = video.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
$.confirm.create({
|
||||
title: lang["Compress"] + ' : ' + video.filename,
|
||||
body: `${lang.CompressVideoMsg}<br><br><div class="row"><video class="video_video" autoplay loop controls><source src="${videoEndpoint}" type="video/${ext}"></video></div>`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-compress"></i> ' + lang.Compress,
|
||||
class: 'btn-primary btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
compressVideo(video)
|
||||
}
|
||||
});
|
||||
return false;
|
||||
})
|
||||
.on('click','.archive-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
var unarchive = $(this).hasClass('status-archived')
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}${undefined}`]
|
||||
var ext = video.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
if(unarchive){
|
||||
unarchiveVideo(video)
|
||||
}else{
|
||||
// $.confirm.create({
|
||||
// title: lang["Archive"] + ' : ' + video.filename,
|
||||
// body: `${lang.ArchiveVideoMsg}<br><br><div class="row"><video class="video_video" autoplay loop controls><source src="${videoEndpoint}" type="video/${ext}"></video></div>`,
|
||||
// clickOptions: {
|
||||
// title: '<i class="fa fa-lock"></i> ' + lang.Archive,
|
||||
// class: 'btn-primary btn-sm'
|
||||
// },
|
||||
// clickCallback: function(){
|
||||
archiveVideo(video)
|
||||
// }
|
||||
// });
|
||||
}
|
||||
return false;
|
||||
})
|
||||
.on('click','.fix-video',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this).parents('[data-mid]')
|
||||
var monitorId = el.attr('data-mid')
|
||||
var videoTime = el.attr('data-time')
|
||||
var video = loadedVideosInMemory[`${monitorId}${videoTime}${undefined}`]
|
||||
var ext = video.filename.split('.')
|
||||
ext = ext[ext.length - 1]
|
||||
var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
$.confirm.create({
|
||||
title: lang["Fix Video"] + ' : ' + video.filename,
|
||||
body: `${lang.FixVideoMsg}<br><br><div class="row"><video class="video_video" autoplay loop controls><source src="${videoEndpoint}" type="video/${ext}"></video></div>`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-wrench"></i> ' + lang.Fix,
|
||||
class: 'btn-danger btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
$.getJSON(videoEndpoint + '/fix',function(data){
|
||||
if(data.ok){
|
||||
console.log('Video Fixed')
|
||||
}else{
|
||||
console.log('Video Not Fixed',data,videoEndpoint)
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
return false;
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
function createVideoPlayerTab(video){
|
||||
// not real one, dummy for wallvideoview.
|
||||
var videoEndpoint = getApiPrefix(`videos`) + '/' + video.mid + '/' + video.filename
|
||||
$.confirm.create({
|
||||
title: lang["Download"],
|
||||
body: `<div class="text-center mb-2"><h6 class="mb-0">${loadedMonitors[video.mid].name}</h6><small>${video.time}</small></div><video style="border-radius:10px;max-width:100%;" autoplay controls playsinline src="${videoEndpoint}"></video>`,
|
||||
clickOptions: {
|
||||
title: '<i class="fa fa-download"></i> ' + lang.Download,
|
||||
class: 'btn-success btn-sm'
|
||||
},
|
||||
clickCallback: function(){
|
||||
downloadFile(videoEndpoint, video.filename)
|
||||
}
|
||||
});
|
||||
}
|
||||
$(document).ready(function(){
|
||||
loadMonitorsIntoMemory(function(data){
|
||||
openTab('timeline')
|
||||
onDashboardReadyExecute()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,299 @@
|
|||
var loadedMonitors = {}
|
||||
var selectedMonitors = {}
|
||||
var selectedMonitorsCount = 0
|
||||
$(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 featureIsActivated(showNotice){
|
||||
if(userHasSubscribed){
|
||||
return true
|
||||
}else{
|
||||
if(showNotice){
|
||||
new PNotify({
|
||||
title: lang.activationRequired,
|
||||
text: lang.featureRequiresActivationText,
|
||||
type: 'warning'
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
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 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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getMonitorListItem(monitorId){
|
||||
return wallViewMonitorList.find(`[select-monitor="${monitorId}"]`)
|
||||
}
|
||||
|
||||
function selectMonitor(monitorId, css){
|
||||
css = css || {};
|
||||
var isSelected = selectedMonitors[monitorId]
|
||||
if(isSelected)return;
|
||||
var numberOfSelected = Object.keys(selectedMonitors)
|
||||
if(numberOfSelected > 3 && !featureIsActivated(true)){
|
||||
return
|
||||
}
|
||||
++selectedMonitorsCount
|
||||
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=/"></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')
|
||||
}
|
||||
function deselectMonitor(monitorId){
|
||||
--selectedMonitorsCount
|
||||
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(),
|
||||
}
|
||||
})
|
||||
})
|
||||
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(selectedMonitorsCount === 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){
|
||||
selectMonitor(monitorId)
|
||||
})
|
||||
autoPlaceCurrentMonitorItems()
|
||||
displayInfoScreen()
|
||||
saveLayout()
|
||||
}
|
||||
|
||||
function closeAllMonitors(){
|
||||
$.each(loadedMonitors,function(monitorId, monitor){
|
||||
deselectMonitor(monitorId)
|
||||
})
|
||||
displayInfoScreen()
|
||||
saveLayout()
|
||||
}
|
||||
|
||||
drawMonitorList().then(() => {
|
||||
loadSavedLayout()
|
||||
setTimeout(() => {
|
||||
theWindow.resize(() => {
|
||||
onWindowResize()
|
||||
saveLayout()
|
||||
})
|
||||
},500)
|
||||
})
|
||||
$('body')
|
||||
.on('click', '[select-monitor]', function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this);
|
||||
var monitorId = el.attr('select-monitor')
|
||||
var isSelected = selectedMonitors[monitorId]
|
||||
if(isSelected){
|
||||
deselectMonitor(monitorId)
|
||||
}else{
|
||||
selectMonitor(monitorId)
|
||||
}
|
||||
displayInfoScreen()
|
||||
saveLayout()
|
||||
})
|
||||
.on('click', '.open-wallview', function(e){
|
||||
e.preventDefault()
|
||||
var windowName = getWindowName();
|
||||
if(isNaN(windowName)){
|
||||
windowName = windowName + '2'
|
||||
}else{
|
||||
windowName = `${parseInt(windowName) + 1}`
|
||||
}
|
||||
createWallViewWindow(windowName)
|
||||
})
|
||||
.on('click', '.wallview-autoplace', function(e){
|
||||
e.preventDefault()
|
||||
autoPlaceCurrentMonitorItems()
|
||||
saveLayout()
|
||||
})
|
||||
.on('click', '.wallview-item-close', function(e){
|
||||
e.preventDefault()
|
||||
var monitorId = $(this).parents('[live-stream]').attr('live-stream')
|
||||
deselectMonitor(monitorId)
|
||||
})
|
||||
.on('click', '.wallview-open-all', function(e){
|
||||
e.preventDefault()
|
||||
openAllMonitors()
|
||||
})
|
||||
.on('click', '.wallview-close-all', function(e){
|
||||
e.preventDefault()
|
||||
closeAllMonitors()
|
||||
})
|
||||
})
|
|
@ -7,7 +7,6 @@ $(document).ready(function(){
|
|||
var remoteDashboardLinkButton = easyRemoteAccessTab.find('.remote-dashboard-link')
|
||||
var loadingRegistration = false
|
||||
var statusConnections = {}
|
||||
var currentlyRegisteredP2PServer = currentlySelectedP2PServerId ? currentlySelectedP2PServerId + '' : undefined
|
||||
function copyToClipboard(str) {
|
||||
const el = document.createElement('textarea');
|
||||
el.value = str;
|
||||
|
@ -72,37 +71,11 @@ $(document).ready(function(){
|
|||
loadingRegistration = false
|
||||
easyRemoteAccessTab.find('.remote-dashboard-link').html(`<i class="fa fa-external-link"></i> ` + lang['Open Remote Dashboard'])
|
||||
easyRemoteAccessTab.find('.remote-dashboard-link-copy').html(`<i class="fa fa-copy"></i> ` + lang['Copy Remote Link'])
|
||||
displayCurrentlySelectedInternally()
|
||||
}
|
||||
function displayCurrentlySelectedInternally(){
|
||||
var selectedServer = p2pServerList[currentlyRegisteredP2PServer]
|
||||
if(selectedServer){
|
||||
var key = selectedServer.key
|
||||
var cardEl = easyRemoteAccessTab.find(`[drawn-id="${key}"]`)
|
||||
easyRemoteAccessTab.find(`[drawn-id].selected`).removeClass('selected')
|
||||
cardEl.addClass('selected')
|
||||
setCurrentRemoteLink()
|
||||
}
|
||||
}
|
||||
function makeHostLink(selectedServer,apiKey){
|
||||
var href = `https://${selectedServer.host}:${selectedServer.webPort == 80 ? 443 : selectedServer.webPort}/s/${apiKey}/`
|
||||
return href
|
||||
}
|
||||
function setCurrentRemoteLink(){
|
||||
var apiKey = easyRemoteAccessForm.find('[name="p2pApiKey"]').val()
|
||||
var selectedServer = p2pServerList[currentlyRegisteredP2PServer]
|
||||
console.log(selectedServer,currentlySelectedP2PServerId,p2pServerList)
|
||||
if(selectedServer && selectedServer.host){
|
||||
var href = makeHostLink(selectedServer,apiKey)
|
||||
remoteDashboardLinkButton.attr('href',href)
|
||||
}else{
|
||||
new PNotify({
|
||||
type: 'warning',
|
||||
title: lang['P2P Server Not Selected'],
|
||||
text: lang.p2pServerNotSelectedText,
|
||||
})
|
||||
}
|
||||
}
|
||||
function beginAllStatusConnections(){
|
||||
$.each(p2pServerList,function(key,server){
|
||||
server.key = key
|
||||
|
@ -127,6 +100,14 @@ $(document).ready(function(){
|
|||
toggleAffected.hide()
|
||||
}
|
||||
}
|
||||
function getSelectedServers(){
|
||||
var theArray = [];
|
||||
$(`[drawn-id].active`).each(function(){
|
||||
var theKey = $(this).attr('drawn-id')
|
||||
theArray.push(theKey)
|
||||
})
|
||||
return theArray
|
||||
}
|
||||
p2pEnabledSwitch.change(setVisibilityForList)
|
||||
easyRemoteAccessTab.find('.submit').click(function(){
|
||||
easyRemoteAccessForm.submit()
|
||||
|
@ -135,39 +116,51 @@ $(document).ready(function(){
|
|||
e.preventDefault()
|
||||
var formValues = $(this).serializeObject()
|
||||
disableForm()
|
||||
formValues.p2pHostSelected = currentlySelectedP2PServerId
|
||||
// formValues.p2pHostSelected = currentlySelectedP2PServerId
|
||||
formValues.p2pHostMultiSelected = getSelectedServers()
|
||||
console.log(formValues)
|
||||
$.post(superApiPrefix + $user.sessionKey + '/p2p/save',{
|
||||
data: JSON.stringify(formValues)
|
||||
},function(data){
|
||||
console.log(data)
|
||||
if(data.ok){
|
||||
currentlyRegisteredP2PServer = currentlySelectedP2PServerId + ''
|
||||
new PNotify({
|
||||
type: 'success',
|
||||
title: lang['P2P Settings Applied'],
|
||||
text: lang.p2pSettingsText1,
|
||||
})
|
||||
setCurrentRemoteLink()
|
||||
setTimeout(enableForm,5000)
|
||||
}
|
||||
})
|
||||
return false
|
||||
})
|
||||
easyRemoteAccessForm.on('click','[drawn-id]',function(){
|
||||
easyRemoteAccessTab.on('click','.activate-remote-selection',function(e){
|
||||
e.preventDefault()
|
||||
var el = $(this)
|
||||
var p2pServerId = el.attr('drawn-id')
|
||||
easyRemoteAccessForm.find('[drawn-id]').removeClass('active')
|
||||
el.addClass('active')
|
||||
currentlySelectedP2PServerId = p2pServerId
|
||||
var parent = el.parents('[drawn-id]')
|
||||
var drawnId = parent.attr('drawn-id')
|
||||
var alreadyActive = parent.hasClass('active')
|
||||
var selectedServer = p2pServerList[drawnId]
|
||||
var isWss = selectedServer.p2pPort == 443
|
||||
console.log(selectedServer)
|
||||
if(alreadyActive){
|
||||
parent.removeClass('active')
|
||||
}else{
|
||||
parent.addClass('active')
|
||||
const drawnIdToDisable = isWss ? drawnId.replace('-ssl','') : `${drawnId}-ssl`
|
||||
console.log(drawnIdToDisable)
|
||||
easyRemoteAccessTab.find(`[drawn-id="${drawnIdToDisable}"]`).removeClass('active')
|
||||
}
|
||||
return false;
|
||||
})
|
||||
easyRemoteAccessTab.on('click','.remote-dashboard-link-copy',function(e){
|
||||
e.preventDefault()
|
||||
if(!loadingRegistration){
|
||||
var parent = $(this).parents('[drawn-id]')
|
||||
var drawnId = parent.attr('drawn-id')
|
||||
var apiKey = easyRemoteAccessForm.find('[name="p2pApiKey"]').val()
|
||||
var selectedServer = p2pServerList[currentlyRegisteredP2PServer]
|
||||
console.log(selectedServer,currentlySelectedP2PServerId,p2pServerList)
|
||||
if(selectedServer && selectedServer.host){
|
||||
var selectedServer = p2pServerList[drawnId]
|
||||
if(parent.hasClass('active') && selectedServer && selectedServer.host){
|
||||
var href = makeHostLink(selectedServer,apiKey)
|
||||
copyToClipboard(href)
|
||||
new PNotify({
|
||||
|
@ -183,8 +176,24 @@ $(document).ready(function(){
|
|||
})
|
||||
}
|
||||
}
|
||||
e.stopPropagation()
|
||||
return false;
|
||||
})
|
||||
easyRemoteAccessTab.on('click','.remote-dashboard-link',function(e){
|
||||
e.preventDefault()
|
||||
if(!loadingRegistration){
|
||||
var parent = $(this).parents('[drawn-id]')
|
||||
var drawnId = parent.attr('drawn-id')
|
||||
var apiKey = easyRemoteAccessForm.find('[name="p2pApiKey"]').val()
|
||||
var selectedServer = p2pServerList[drawnId]
|
||||
console.log(selectedServer,parent.hasClass('active'),parent.length)
|
||||
if(parent.hasClass('active') && selectedServer && selectedServer.host){
|
||||
var href = makeHostLink(selectedServer,apiKey)
|
||||
window.open(href, '_blank').focus();
|
||||
}
|
||||
}
|
||||
e.stopPropagation()
|
||||
return false;
|
||||
})
|
||||
setVisibilityForList()
|
||||
displayCurrentlySelectedInternally()
|
||||
})
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<% const p2pServerList = config.p2pServerList || {}
|
||||
const selectedServer = p2pServerList[config.p2pHostSelected]
|
||||
<%
|
||||
const p2pServerList = config.p2pServerList || {}
|
||||
const selectedServers = config.p2pHostMultiSelected
|
||||
const multipleSelected = selectedServers instanceof Array && selectedServers.length > 0;
|
||||
%>
|
||||
<style>
|
||||
.epic-text-filter {
|
||||
|
@ -19,8 +21,9 @@
|
|||
</style>
|
||||
<script>
|
||||
window.p2pServerList = <%- JSON.stringify(p2pServerList) %>
|
||||
window.multipleSelected = <%- multipleSelected %>
|
||||
window.selectedServers = <%- JSON.stringify(selectedServers) %>
|
||||
window.useBetterP2P = <%- !!config.useBetterP2P %>
|
||||
window.currentlySelectedP2PServerId = `<%- config.p2pHostSelected %>`
|
||||
</script>
|
||||
<link rel="stylesheet" href="<%-window.libURL%>assets/css/super.easyRemoteAccess.css">
|
||||
<form>
|
||||
|
@ -40,12 +43,6 @@
|
|||
<div class="form-group p2p-toggle-affected">
|
||||
<input placeholder="<%-lang['P2P API Key']%>" class="form-control btn btn-dark text-left text-white" type="text" name="p2pApiKey" value="<%- config.p2pApiKey %>">
|
||||
</div>
|
||||
<div class="form-group p2p-toggle-affected">
|
||||
<div class="btn-group d-flex flex-row">
|
||||
<a target="_blank" href="#" class="flex-grow-1 btn btn-default remote-dashboard-link-copy"><i class="fa fa-copy"></i> <%- lang['Copy Remote Link'] %></a>
|
||||
<a target="_blank" href="#" class="flex-grow-1 btn btn-info remote-dashboard-link" target="_blank"><i class="fa fa-external-link"></i> <%- lang['Open Remote Dashboard'] %></a>
|
||||
</div>
|
||||
</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>
|
||||
|
@ -72,7 +69,7 @@
|
|||
if(!config.useBetterP2P && details.v2)return;
|
||||
%>
|
||||
<div class="col-md-4">
|
||||
<div class="card bg-dark cursor-pointer text-white mb-4 <% if(config.p2pHostSelected === key){ %>active<% } %>" drawn-id="<%- key %>">
|
||||
<div class="card bg-dark cursor-pointer text-white mb-4 <% if((!multipleSelected && config.p2pHostSelected === key) || multipleSelected && selectedServers.indexOf(key) > -1){ %>active<% } %>" drawn-id="<%- key %>">
|
||||
<div class="card-header" style="min-height:auto">
|
||||
<span class="badge badge-sm badge-danger pull-right selected-badge"><%- lang.Selected %></span>
|
||||
<span class="badge badge-sm badge-info name-badge"><%- details.name.toUpperCase() %></span>
|
||||
|
@ -107,7 +104,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="card-footer" style="min-height:auto">
|
||||
<a class="btn badge badge-sm badge-warning geo-badge" target="_blank" href="<%- `https://www.google.ca/maps/@${details.location.lat},${details.location.lon},15z` %>"><i class="fa fa-map-marker"></i> <%- details.location.lat %>, <%- details.location.lon %></a>
|
||||
<div>
|
||||
<a target="_blank" href="#" class="d-block btn btn-default activate-remote-selection"><i class="fa fa-check"></i> <%- lang['Activate'] %></a>
|
||||
<a target="_blank" href="#" class="d-block btn btn-default remote-dashboard-link-copy"><i class="fa fa-copy"></i> <%- lang['Copy Remote Link'] %></a>
|
||||
<a target="_blank" href="#" class="d-block btn btn-info remote-dashboard-link" target="_blank"><i class="fa fa-external-link"></i> <%- lang['Open Remote Dashboard'] %></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -115,7 +116,6 @@
|
|||
</div>
|
||||
<div class="form-group p2p-toggle-affected">
|
||||
<div class="btn-group d-flex flex-row">
|
||||
<a target="_blank" href="#" class="flex-grow-1 btn btn-info remote-dashboard-link" target="_blank"><i class="fa fa-external-link"></i> <%- lang['Open Remote Dashboard'] %></a>
|
||||
<a href="#" class="submit flex-grow-1 btn btn-success"><i class="fa fa-check"></i> <%- lang.Save %></a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -21,12 +21,15 @@
|
|||
<script src="<%-window.libURL%>assets/vendor/js/clock.js"></script>
|
||||
<script src="<%-window.libURL%>assets/vendor/js/vis.min.js" async></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.dashboard-base.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.dashboard-base.buttons.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.extenders.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.websocket.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.videos.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.videos.buttons.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.videoPlayer.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.livePlayer.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.accountSettings.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.alternateLogins.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.subAccountManager.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.apiKeys.js"></script>
|
||||
<script src="<%-window.libURL%>assets/js/bs5.monitorSettings.js"></script>
|
||||
|
|
|
@ -42,12 +42,16 @@
|
|||
</main>
|
||||
|
||||
<script>
|
||||
var monitorSettingsAdditionalInputMapFieldHtml = `<% Object.keys(define["Monitor Settings Additional Input Map"].blocks).forEach(function(blockKey){
|
||||
drawBlock(define["Monitor Settings Additional Input Map"].blocks[blockKey])
|
||||
}) %>`
|
||||
var monitorSettingsAdditionalInputMapFieldHtml = `<% Object.keys(define["Monitor Settings Additional Input Map"].blocks).forEach(function(blockKey){ %>
|
||||
<%- include('drawBlock', {
|
||||
theBlock: define["Monitor Settings Additional Input Map"].blocks[blockKey]
|
||||
}) %>
|
||||
<% }) %>`
|
||||
</script>
|
||||
<script>
|
||||
var monitorSettingsAdditionalStreamChannelFieldHtml = `<% Object.keys(define["Monitor Settings Additional Stream Channel"].blocks).forEach(function(blockKey){
|
||||
drawBlock(define["Monitor Settings Additional Stream Channel"].blocks[blockKey])
|
||||
}) %>`
|
||||
var monitorSettingsAdditionalStreamChannelFieldHtml = `<% Object.keys(define["Monitor Settings Additional Stream Channel"].blocks).forEach(function(blockKey){ %>
|
||||
<%- include('drawBlock', {
|
||||
theBlock: define["Monitor Settings Additional Stream Channel"].blocks[blockKey]
|
||||
}) %>
|
||||
<% }) %>`
|
||||
</script>
|
||||
|
|
|
@ -17,10 +17,7 @@
|
|||
var buildOptions
|
||||
%>
|
||||
<form class="dark">
|
||||
<%- include('home/fieldBuilders'); %>
|
||||
<%
|
||||
drawBlock(define['LDAP'].blocks.LDAP)
|
||||
%>
|
||||
<%- include('home/drawBlock', { theBlock: define['LDAP'].blocks.LDAP }) %>
|
||||
</form>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
<title><%- lang.Shinobi %></title>
|
||||
<%
|
||||
var forceUrlPrefix
|
||||
var urlPrefix = ``
|
||||
var targetPort = config.ssl && config.ssl.port && protocol === 'https' ? config.ssl.port : config.port
|
||||
var addonsEnabled = {}
|
||||
var rawAddonList = decodeURI(data.addon || '').split('|');
|
||||
rawAddonList.forEach(function(piece){
|
||||
var pieceParts = piece.split('=');
|
||||
var key = pieceParts[0];
|
||||
var value = pieceParts[1] || true;
|
||||
addonsEnabled[key] = value;
|
||||
});
|
||||
function getAddon(addonTag){
|
||||
return addonsEnabled[addonTag];
|
||||
}
|
||||
var streamWidth = parseInt(getAddon('width')) || 640
|
||||
var streamHeight = parseInt(getAddon('height')) || 480
|
||||
var hasGUI = getAddon('gui')
|
||||
var isFullscreen = getAddon('fullscreen')
|
||||
var isRelativeUrl = getAddon('relative')
|
||||
if(forceUrlPrefix){
|
||||
urlPrefix = forceUrlPrefix
|
||||
}else if(isRelativeUrl){
|
||||
urlPrefix = ''
|
||||
}else if(config.baseURL){
|
||||
urlPrefix = config.baseURL
|
||||
}else if(!targetPort || targetPort === '' || targetPort == 80 || targetPort == 443){
|
||||
urlPrefix = baseUrl
|
||||
}else{
|
||||
urlPrefix = `${baseUrl}:${targetPort}`
|
||||
}
|
||||
if(urlPrefix.endsWith('/') === false){
|
||||
urlPrefix += '/'
|
||||
}
|
||||
var originalURL = `${urlPrefix}`
|
||||
var libURL = config.baseURL;
|
||||
if(!libURL)libURL = originalURL;
|
||||
%>
|
||||
<%- include('blocks/header-favicon') %>
|
||||
<script>window.libURL="<%- libURL %>";</script>
|
||||
<script>window.$user=<%- JSON.stringify($user) %>;</script>
|
||||
<script>window.lang=<%- JSON.stringify(lang) %>;</script>
|
||||
<script>window.definitions=<%- JSON.stringify(define) %>;</script>
|
||||
<script>window.urlPrefix = "<%- urlPrefix || '' %>";</script>
|
||||
<script>window.groupKey = "<%- groupKey || '' %>";</script>
|
||||
<script>window.authKey = "<%- 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>
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/vendor/font-awesome/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/vendor/bootstrap5/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/vendor/daterangepicker.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/vendor/vis.min.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/css/bs5.timeline.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/css/dashboard.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/vendor/jquery-ui.min.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/vendor/pnotify.custom.min.css">
|
||||
<body class="bg-darker">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<main id="pageTabContainer" class="col-md-12 ms-sm-auto col-lg-12 px-md-4">
|
||||
<%- include('blocks/home/timeline.ejs') %>
|
||||
<%- include('blocks/confirm.ejs') %>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<div style="display:none" id="temp"></div>
|
||||
<a id="floating-back-button" class="go-back" style="display:none"><i class="fa fa-arrow-left"></i></a>
|
||||
<script src="<%- urlPrefix %>assets/vendor/moment-with-locales.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/vendor/js/daterangepicker.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/vendor/js/vis.min.js" async></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.dashboard-base.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.zipAndDownload.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.monitorsUtils.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.videos.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.confirm.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.wallvideoview.js"></script>
|
|
@ -0,0 +1,78 @@
|
|||
<title><%- lang.Shinobi %></title>
|
||||
<%
|
||||
var forceUrlPrefix
|
||||
var urlPrefix = ``
|
||||
var targetPort = config.ssl && config.ssl.port && protocol === 'https' ? config.ssl.port : config.port
|
||||
var addonsEnabled = {}
|
||||
var rawAddonList = decodeURI(data.addon || '').split('|');
|
||||
rawAddonList.forEach(function(piece){
|
||||
var pieceParts = piece.split('=');
|
||||
var key = pieceParts[0];
|
||||
var value = pieceParts[1] || true;
|
||||
addonsEnabled[key] = value;
|
||||
});
|
||||
function getAddon(addonTag){
|
||||
return addonsEnabled[addonTag];
|
||||
}
|
||||
var streamWidth = parseInt(getAddon('width')) || 640
|
||||
var streamHeight = parseInt(getAddon('height')) || 480
|
||||
var hasGUI = getAddon('gui')
|
||||
var isFullscreen = getAddon('fullscreen')
|
||||
var isRelativeUrl = getAddon('relative')
|
||||
if(forceUrlPrefix){
|
||||
urlPrefix = forceUrlPrefix
|
||||
}else if(isRelativeUrl){
|
||||
urlPrefix = ''
|
||||
}else if(config.baseURL){
|
||||
urlPrefix = config.baseURL
|
||||
}else if(!targetPort || targetPort === '' || targetPort == 80 || targetPort == 443){
|
||||
urlPrefix = baseUrl
|
||||
}else{
|
||||
urlPrefix = `${baseUrl}:${targetPort}`
|
||||
}
|
||||
if(urlPrefix.endsWith('/') === false){
|
||||
urlPrefix += '/'
|
||||
}
|
||||
var originalURL = `${urlPrefix}`
|
||||
%>
|
||||
<%- include('blocks/header-favicon') %>
|
||||
<script>window.$user=<%- JSON.stringify($user) %>;</script>
|
||||
<script>window.urlPrefix = "<%- urlPrefix || '' %>";</script>
|
||||
<script>window.groupKey = "<%- groupKey || '' %>";</script>
|
||||
<script>window.authKey = "<%- 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>
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/vendor/font-awesome/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/vendor/bootstrap5/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/css/bs5.wallview.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/vendor/jquery-ui.min.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/vendor/pnotify.custom.min.css">
|
||||
<div id="wallview-container">
|
||||
<div id="wallview-info-screen" class="justify-content-center align-self-center" style="display:none">
|
||||
<div class="justify-content-center align-self-center text-center col-3 text-white">
|
||||
<i class="fa fa-info-circle fa-5x mb-3"></i>
|
||||
<h4><%- lang.openWallViewInfo %></h4>
|
||||
</div>
|
||||
</div>
|
||||
<div id="wallview-canvas">
|
||||
|
||||
</div>
|
||||
<div id="wallview-controls" class="text-end">
|
||||
<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>
|
||||
<a class="btn btn-primary open-wallview" href="#"><%- lang['New Wall Display'] %></a>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <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.wallview.js"></script>
|
Loading…
Reference in New Issue