Merge branch 'dev' into 'master'

Fragrant Foliage

See merge request Shinobi-Systems/Shinobi!503
alpha
Moe 2024-08-09 18:03:36 +00:00
commit 947de85a10
54 changed files with 3496 additions and 4457 deletions

View File

@ -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

View File

@ -1,3 +1,9 @@
⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
# OUTDATED, Newest Docker method here :
https://gitlab.com/Shinobi-Systems/ShinobiDocker
⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
# Install Shinobi with Docker # 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. **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.

View File

@ -5750,6 +5750,10 @@ module.exports = function(s,config,lang){
name: lang['Can Get Monitors'], name: lang['Can Get Monitors'],
value: 'get_monitors', value: 'get_monitors',
}, },
{
name: lang['Can Edit Monitors'],
value: 'edit_monitors',
},
{ {
name: lang['Can Control Monitors'], name: lang['Can Control Monitors'],
value: 'control_monitors', value: 'control_monitors',
@ -6606,7 +6610,7 @@ module.exports = function(s,config,lang){
{ {
"name": "ip", "name": "ip",
"field": lang['IP Address'], "field": lang['IP Address'],
"description": lang[lang["fieldTextIp"]], "description": lang.fieldTextIp,
"example": "10.1.100.1-10.1.100.254", "example": "10.1.100.1-10.1.100.254",
}, },
{ {
@ -6618,11 +6622,13 @@ module.exports = function(s,config,lang){
{ {
"name": "user", "name": "user",
"field": lang['Camera Username'], "field": lang['Camera Username'],
"description": lang.fieldTextOnvifScanCameraUsername,
"placeholder": "Can be left blank.", "placeholder": "Can be left blank.",
}, },
{ {
"name": "pass", "name": "pass",
"field": lang['Camera Password'], "field": lang['Camera Password'],
"description": lang.fieldTextOnvifScanCameraPassword,
"fieldType": "password", "fieldType": "password",
}, },
{ {
@ -6631,8 +6637,13 @@ module.exports = function(s,config,lang){
{ {
"fieldType": "btn", "fieldType": "btn",
"forForm": true, "forForm": true,
"class": `btn-block btn-success`, "class": `btn-success start-scan`,
"btnContent": `${lang['Search']}<span class="_loading" style="display:none"> &nbsp; <i class="fa fa-pulse fa-spinner"></i></span>`, "btnContent": `${lang['Search']}`,
},
{
"fieldType": "btn",
"class": `btn-danger stop-scan d-none`,
"btnContent": `${lang['Stop']}`,
}, },
{ {
"fieldType": "btn", "fieldType": "btn",
@ -6653,18 +6664,7 @@ module.exports = function(s,config,lang){
"class": "onvif_result row", "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": { "Camera Probe": {
@ -7696,6 +7696,11 @@ module.exports = function(s,config,lang){
pageOpen: 'liveGrid', pageOpen: 'liveGrid',
addUl: true, addUl: true,
ulItems: [ ulItems: [
{
label: lang['Open Wall Display'],
class: 'open-wallview cursor-pointer',
color: 'blue',
},
{ {
label: lang['Open All Monitors'], label: lang['Open All Monitors'],
class: 'open-all-monitors cursor-pointer', class: 'open-all-monitors cursor-pointer',
@ -9191,6 +9196,12 @@ module.exports = function(s,config,lang){
<div class="btn-group"> <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> <a class="btn btn-sm btn-primary" id="timeline-date-selector" title="${lang.Date}"><i class="fa fa-calendar"></i></a>
</div> </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> </div>
`, `,
}, },

View File

@ -20,6 +20,8 @@
"Geolocation": "Geolocation", "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.", "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", "playUntilVideoEnd": "Play until video end",
"Monitor Saved": "Monitor Saved",
"Auto Placement": "Auto Placement",
"Unmute": "Unmute", "Unmute": "Unmute",
"byUser": "by user", "byUser": "by user",
"accountDeleted": "Account Deleted", "accountDeleted": "Account Deleted",
@ -135,7 +137,12 @@
"Viewing Server Stats": "Viewing Server Stats", "Viewing Server Stats": "Viewing Server Stats",
"Connected Users": "Connected Users", "Connected Users": "Connected Users",
"Registered Servers": "Registered Servers", "Registered Servers": "Registered Servers",
"Open All": "Open All",
"Close All": "Close All",
"Open All Monitors": "Open All Monitors", "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", "Accounts": "Accounts",
"Settings": "Settings", "Settings": "Settings",
"Count Objects only inside Regions": "Count Objects only inside Regions", "Count Objects only inside Regions": "Count Objects only inside Regions",
@ -298,6 +305,7 @@
"Can Get Logs": "Can Get Logs", "Can Get Logs": "Can Get Logs",
"Can Authenticate Websocket": "Can Authenticate Websocket", "Can Authenticate Websocket": "Can Authenticate Websocket",
"Can Control Monitors": "Can Control Monitors", "Can Control Monitors": "Can Control Monitors",
"Can Edit Monitors": "Can Edit Monitors",
"Can View Snapshots": "Can View Snapshots", "Can View Snapshots": "Can View Snapshots",
"Can View Streams": "Can View Streams", "Can View Streams": "Can View Streams",
"Can View Videos": "Can View Videos", "Can View Videos": "Can View Videos",
@ -417,6 +425,8 @@
"Audio streams only": "Audio streams only", "Audio streams only": "Audio streams only",
"Audio stream only from first feed": "Audio stream only from first feed", "Audio stream only from first feed": "Audio stream only from first feed",
"sorryNothingWasFound": "Sorry, nothing was found.", "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", "Event Occurred": "Event Occurred",
"ONVIF Port": "ONVIF Port", "ONVIF Port": "ONVIF Port",
"ONVIF Scanner": "ONVIF Scanner", "ONVIF Scanner": "ONVIF Scanner",
@ -424,9 +434,10 @@
"ONVIFEventsNotAvailable": "ONVIF Events not Available", "ONVIFEventsNotAvailable": "ONVIF Events not Available",
"ONVIFEventsNotAvailableText1": "This service may not be available for this camera or ONVIF has not initialized yet.", "ONVIFEventsNotAvailableText1": "This service may not be available for this camera or ONVIF has not initialized yet.",
"ONVIFnotCompliantProfileT": "Camera is not ONVIF Profile T Compliant", "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.", "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.", "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", "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.", "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", "Range or Single": "Range or Single",
@ -1426,6 +1437,7 @@
"Select atleast one monitor to delete": "Select atleast one monitor to delete.", "Select atleast one monitor to delete": "Select atleast one monitor to delete.",
"Use Built-In": "Use Built-In", "Use Built-In": "Use Built-In",
"Add Cameras": "Add Cameras", "Add Cameras": "Add Cameras",
"addAllCamerasText": "You are about to add 9001 cameras to your system.",
"Add Camera": "Add Camera", "Add Camera": "Add Camera",
"Delete Camera": "Delete Camera", "Delete Camera": "Delete Camera",
"Event Rules": "Event Rules", "Event Rules": "Event Rules",
@ -1448,7 +1460,7 @@
"Original Choice": "Original Choice", "Original Choice": "Original Choice",
"Legacy Webhook": "Legacy Webhook", "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.", "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", "Telegram": "Telegram",
"Before": "Before", "Before": "Before",
"After": "After", "After": "After",

View File

@ -106,6 +106,8 @@ module.exports = function(s,config,lang){
permissions: {} permissions: {}
}) })
callback(err,user,true) callback(err,user,true)
}else{
callback(lang['Not Authorized'],null,false)
} }
}) })
} }

View File

@ -150,8 +150,8 @@ module.exports = (s,config,lang,app) => {
alternateLoginsFieldList[alternateLoginsFieldList.length - 1].btns.push({ alternateLoginsFieldList[alternateLoginsFieldList.length - 1].btns.push({
"class": `btn-info ldap-sign-in`, "class": `btn-info ldap-sign-in`,
"btnContent": `<i class="fa fa-group"></i> &nbsp; ${lang['Link LDAP Account']}`, "btnContent": `<i class="fa fa-group"></i> &nbsp; ${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) * API : Add Token Window (Sign-In to LDAP) (GET)

View File

@ -179,7 +179,7 @@ module.exports = function(s,config,lang,app,io){
} }
s.insertDatabaseRow(monitorConfig,insert) s.insertDatabaseRow(monitorConfig,insert)
s.insertCompletedVideoExtensions.forEach(function(extender){ s.insertCompletedVideoExtensions.forEach(function(extender){
extender(monitorConfig,insert) extender(activeMonitor, monitorConfig, insert)
}) })
//purge over max //purge over max
s.purgeDiskForGroup(data.ke) s.purgeDiskForGroup(data.ke)

View File

@ -8,6 +8,9 @@ let config = workerData.config
let lang = workerData.lang let lang = workerData.lang
let sslInfo = config.ssl || {} let sslInfo = config.ssl || {}
let remoteConnectionPort = config.easyRemotePort || (sslInfo && sslInfo.port && (sslInfo.enabled !== false) ? sslInfo.port : config.port || 8080) 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 net = require("net")
const bson = require('bson') const bson = require('bson')
const WebSocket = require('cws') const WebSocket = require('cws')
@ -28,7 +31,16 @@ const s = {
parentPort.on('message',(data) => { parentPort.on('message',(data) => {
switch(data.f){ switch(data.f){
case'init': case'init':
initialize() if(multipleSelected){
for(aSelection of config.p2pHostMultiSelected){
clearAllTimeouts(aSelection)
initialize(aSelection)
}
}else{
const singleSelection = config.p2pHostSelected;
clearAllTimeouts(singleSelection)
initialize(singleSelection)
}
break; break;
case'exit': case'exit':
s.debugLog('Closing P2P Connection...') s.debugLog('Closing P2P Connection...')
@ -36,11 +48,11 @@ parentPort.on('message',(data) => {
break; break;
} }
}) })
var socketCheckTimer = null var socketCheckTimer = {}
var heartbeatTimer = null var heartbeatTimer = {}
var heartBeatCheckTimout = null var heartBeatCheckTimout = {}
var onClosedTimeout = null var onClosedTimeout = {}
let stayDisconnected = false let stayDisconnected = {}
const requestConnections = {} const requestConnections = {}
const requestConnectionsData = {} const requestConnectionsData = {}
function getRequestConnection(requestId){ function getRequestConnection(requestId){
@ -48,29 +60,28 @@ function getRequestConnection(requestId){
write: () => {} write: () => {}
} }
} }
function clearAllTimeouts(){ function clearAllTimeouts(p2pServerAddress){
clearInterval(heartbeatTimer) clearInterval(heartbeatTimer[p2pServerAddress])
clearTimeout(heartBeatCheckTimout) clearTimeout(heartBeatCheckTimout[p2pServerAddress])
clearTimeout(onClosedTimeout) clearTimeout(onClosedTimeout[p2pServerAddress])
} }
function startConnection(p2pServerAddress,subscriptionId){ function startConnection(p2pServerAddress,subscriptionId){
let tunnelToP2P let tunnelToP2P
stayDisconnected = false stayDisconnected[p2pServerAddress] = false
const allMessageHandlers = [] const allMessageHandlers = []
async function startWebsocketConnection(key,callback){ async function startWebsocketConnection(key,callback){
s.debugLog(`startWebsocketConnection EXECUTE`,new Error()) s.debugLog(`startWebsocketConnection EXECUTE`,new Error())
console.log('P2P : Connecting to Konekta P2P Server...')
function createWebsocketConnection(){ function createWebsocketConnection(){
clearAllTimeouts()
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
try{ try{
stayDisconnected = true stayDisconnected[p2pServerAddress] = true
if(tunnelToP2P)tunnelToP2P.close() if(tunnelToP2P)tunnelToP2P.close()
}catch(err){ }catch(err){
console.log(err) console.log(err)
} }
tunnelToP2P = new WebSocket(p2pServerAddress); tunnelToP2P = new WebSocket(p2pServerAddress);
stayDisconnected = false; console.log('P2P : Connecting to Konekta P2P Server :', p2pServerAddress)
stayDisconnected[p2pServerAddress] = false;
tunnelToP2P.on('open', function(){ tunnelToP2P.on('open', function(){
resolve(tunnelToP2P) resolve(tunnelToP2P)
}) })
@ -81,7 +92,7 @@ function startConnection(p2pServerAddress,subscriptionId){
}) })
tunnelToP2P.on('close', () => { tunnelToP2P.on('close', () => {
console.log(`P2P Connection Closed!`) console.log(`P2P Connection Closed!`)
clearAllTimeouts() clearAllTimeouts(p2pServerAddress)
// onClosedTimeout = setTimeout(() => { // onClosedTimeout = setTimeout(() => {
// disconnectedConnection(); // disconnectedConnection();
// },5000) // },5000)
@ -95,8 +106,8 @@ function startConnection(p2pServerAddress,subscriptionId){
}) })
} }
clearInterval(socketCheckTimer) clearInterval(socketCheckTimer[p2pServerAddress])
socketCheckTimer = setInterval(() => { socketCheckTimer[p2pServerAddress] = setInterval(() => {
// s.debugLog('Tunnel Ready State :',tunnelToP2P.readyState) // s.debugLog('Tunnel Ready State :',tunnelToP2P.readyState)
if(tunnelToP2P.readyState !== 1){ if(tunnelToP2P.readyState !== 1){
s.debugLog('Tunnel NOT Ready! Reconnecting...') s.debugLog('Tunnel NOT Ready! Reconnecting...')
@ -106,10 +117,10 @@ function startConnection(p2pServerAddress,subscriptionId){
}) })
} }
function disconnectedConnection(code,reason){ function disconnectedConnection(code,reason){
s.debugLog('stayDisconnected',stayDisconnected) s.debugLog('stayDisconnected',stayDisconnected[p2pServerAddress])
clearAllTimeouts() clearAllTimeouts()
s.debugLog('DISCONNECTED!') s.debugLog('DISCONNECTED!')
if(stayDisconnected)return; if(stayDisconnected[p2pServerAddress])return;
s.debugLog('RESTARTING!') s.debugLog('RESTARTING!')
setTimeout(() => { setTimeout(() => {
if(tunnelToP2P && tunnelToP2P.readyState !== 1)startWebsocketConnection() if(tunnelToP2P && tunnelToP2P.readyState !== 1)startWebsocketConnection()
@ -122,8 +133,8 @@ function startConnection(p2pServerAddress,subscriptionId){
subscriptionId: subscriptionId, subscriptionId: subscriptionId,
restrictedTo: config.p2pRestrictedTo || [], restrictedTo: config.p2pRestrictedTo || [],
}) })
clearInterval(heartbeatTimer) clearInterval(heartbeatTimer[p2pServerAddress])
heartbeatTimer = setInterval(() => { heartbeatTimer[p2pServerAddress] = setInterval(() => {
sendDataToTunnel({ sendDataToTunnel({
f: 'ping', f: 'ping',
}) })
@ -158,7 +169,7 @@ function startConnection(p2pServerAddress,subscriptionId){
// remotesocket.off('close') // remotesocket.off('close')
// requestConnections[requestId].end() // requestConnections[requestId].end()
// } // }
const responseTunnel = await getResponseTunnel(requestId) const responseTunnel = await getResponseTunnel(requestId, p2pServerAddress)
let remotesocket = new net.Socket(); let remotesocket = new net.Socket();
remotesocket.on('ready',() => { remotesocket.on('ready',() => {
remotesocket.write(initData.buffer) remotesocket.write(initData.buffer)
@ -196,8 +207,8 @@ function startConnection(p2pServerAddress,subscriptionId){
} }
} }
function refreshHeartBeatCheck(){ function refreshHeartBeatCheck(){
clearTimeout(heartBeatCheckTimout) clearTimeout(heartBeatCheckTimout[p2pServerAddress])
heartBeatCheckTimout = setTimeout(() => { heartBeatCheckTimout[p2pServerAddress] = setTimeout(() => {
startWebsocketConnection() startWebsocketConnection()
},1000 * 10 * 1.5) },1000 * 10 * 1.5)
} }
@ -304,15 +315,16 @@ function startConnection(p2pServerAddress,subscriptionId){
onIncomingMessage('disconnect',function(data,requestId){ onIncomingMessage('disconnect',function(data,requestId){
console.log(`FAILED LICENSE CHECK ON P2P`) console.log(`FAILED LICENSE CHECK ON P2P`)
const retryLater = data && data.retryLater; const retryLater = data && data.retryLater;
stayDisconnected = !retryLater stayDisconnected[p2pServerAddress] = !retryLater
if(retryLater)console.log(`Retrying P2P Later...`) if(retryLater)console.log(`Retrying P2P Later...`)
}) })
return tunnelToP2P;
} }
const responseTunnels = {} const responseTunnels = {}
async function getResponseTunnel(originalRequestId){ async function getResponseTunnel(originalRequestId, p2pServerAddress){
return responseTunnels[originalRequestId] || await createResponseTunnel(originalRequestId) return responseTunnels[originalRequestId] || await createResponseTunnel(originalRequestId, p2pServerAddress)
} }
function createResponseTunnel(originalRequestId){ function createResponseTunnel(originalRequestId, p2pServerAddress){
const responseTunnelMessageHandlers = [] const responseTunnelMessageHandlers = []
function onMessage(key,callback){ function onMessage(key,callback){
responseTunnelMessageHandlers.push({ responseTunnelMessageHandlers.push({
@ -321,7 +333,7 @@ function createResponseTunnel(originalRequestId){
}) })
} }
return new Promise((resolve,reject) => { return new Promise((resolve,reject) => {
const responseTunnel = new WebSocket(config.selectedHost); const responseTunnel = new WebSocket(p2pServerAddress);
function sendToResponseTunnel(data){ function sendToResponseTunnel(data){
responseTunnel.send( responseTunnel.send(
bson.serialize(data) bson.serialize(data)
@ -374,10 +386,9 @@ function closeResponseTunnel(originalRequestId){
s.debugLog('closeResponseTunnel',err) s.debugLog('closeResponseTunnel',err)
} }
} }
function initialize(){ function initialize(p2pHostSelected){
const selectedP2PServerId = config.p2pServerList[config.p2pHostSelected] ? config.p2pHostSelected : Object.keys(config.p2pServerList)[0] const selectedP2PServerId = p2pServerList[p2pHostSelected] ? p2pHostSelected : Object.keys(p2pServerList)[0]
const p2pServerDetails = config.p2pServerList[selectedP2PServerId] const p2pServerDetails = p2pServerList[selectedP2PServerId]
const selectedHost = `${p2pServerDetails.secure ? `wss` : 'ws'}://` + p2pServerDetails.host + ':' + p2pServerDetails.p2pPort const selectedHost = `${p2pServerDetails.secure ? `wss` : 'ws'}://` + p2pServerDetails.host + ':' + p2pServerDetails.p2pPort
config.selectedHost = selectedHost startConnection(selectedHost,p2pApiKey)
startConnection(selectedHost,config.p2pApiKey)
} }

View File

@ -389,12 +389,14 @@ module.exports = function(s,config,lang){
},callback) },callback)
} }
const moveToHomePosition = (options,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" const profileToken = options.ProfileToken || "__CURRENT_TOKEN"
return s.runOnvifMethod({ return s.runOnvifMethod({
auth: { auth: {
ke: options.ke, ke: groupKey,
id: options.id, id: monitorId,
service: 'ptz', service: 'ptz',
action: 'gotoHomePosition', action: 'gotoHomePosition',
}, },
@ -537,5 +539,6 @@ module.exports = function(s,config,lang){
moveToPresetPosition, moveToPresetPosition,
moveCameraPtzToMatrix, moveCameraPtzToMatrix,
setHomePositionPreset, setHomePositionPreset,
moveToHomePosition,
} }
} }

View File

@ -50,7 +50,7 @@ module.exports = (s,config,lang) => {
} }
break; break;
default: default:
s.systemLog('CRON.js MESSAGE : ',data) s.debugLog('CRON.js MESSAGE : ',data)
break; break;
} }
}) })

View File

@ -1,99 +1,89 @@
function hasOnvifEventsEnabled(monitorConfig) {
return monitorConfig.details.is_onvif === '1' && monitorConfig.details.onvif_events === '1';
}
module.exports = function (s, config, lang) { module.exports = function (s, config, lang) {
const {Cam} = require("onvif");
const { const {
triggerEvent, triggerEvent,
} = require('./utils.js')(s, config, lang) } = require('./utils.js')(s, config, lang)
const onvifEvents = require("node-onvif-events");
const onvifEventIds = [] function handleEvent(event, monitorConfig, onvifEventLog) {
const onvifEventControllers = {} const eventValue = event.message?.message?.data?.simpleItem?.$?.Value;
const startMotion = async (onvifId,monitorConfig) => { if (eventValue === false) {
const groupKey = monitorConfig.ke onvifEventLog(`ONVIF Event Stopped`, `topic ${event.topic?._}`)
const monitorId = monitorConfig.mid return
const onvifIdKey = `${monitorConfig.mid}${monitorConfig.ke}` }
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 controlBaseUrl = monitorConfig.details.control_base_url || s.buildMonitorUrl(monitorConfig, true)
const controlURLOptions = s.cameraControlOptionsFromUrl(controlBaseUrl, monitorConfig) const controlURLOptions = s.cameraControlOptionsFromUrl(controlBaseUrl, monitorConfig)
const onvifPort = parseInt(monitorConfig.details.onvif_port) || 8000 const onvifPort = parseInt(monitorConfig.details.onvif_port) || 8000
let options = {
id: onvifId, const options = {
hostname: controlURLOptions.host, hostname: controlURLOptions.host,
username: controlURLOptions.username, username: controlURLOptions.username,
password: controlURLOptions.password, password: controlURLOptions.password,
port: onvifPort, port: onvifPort,
}; };
const detector = onvifEventControllers[onvifIdKey] || await onvifEvents.MotionDetector.create(options.id, options);
function onvifEventLog(type,data){ 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({ s.userLog({
ke: groupKey, ke: monitorConfig.key,
mid: monitorId mid: monitorConfig.mid
}, { }, {
type: type, type: type,
msg: data msg: data
}) })
} }
onvifEventLog(`ONVIF Event Detection Listening!`)
try { if (!hasOnvifEventsEnabled(monitorConfig)) {
detector.listen((motion) => { cams[monitorConfig.key]?.removeAllListeners('event')
if (motion) { return
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);
} }
if (cams[monitorConfig.key]) {
onvifEventLog("ONVIF already listening to events")
return;
} }
cams[monitorConfig.key] = configureOnvif(monitorConfig,onvifEventLog);
} }
s.onMonitorStart((monitorConfig) => { s.onMonitorStart((monitorConfig) => {
initializeOnvifEvents(monitorConfig) initializeOnvifEvents(monitorConfig)
}) })
const connectionInfoArray = s.definitions["Monitor Settings"].blocks["Detector"].info const connectionInfoArray = s.definitions["Monitor Settings"].blocks["Detector"].info
connectionInfoArray.splice(2, 0, { connectionInfoArray.splice(2, 0, {
"name": "detail=onvif_events", "name": "detail=onvif_events",

View File

@ -83,6 +83,7 @@ module.exports = (s,config,lang) => {
&& detailString.matrices[0] && detailString.matrices[0]
&& detailString.matrices[0].tag; && detailString.matrices[0].tag;
newString = newString newString = newString
.replace(/{{CONFIDENCE}}/g,d.details.confidence)
.replace(/{{TIME}}/g,d.currentTimestamp) .replace(/{{TIME}}/g,d.currentTimestamp)
.replace(/{{REGION_NAME}}/g,d.details.name) .replace(/{{REGION_NAME}}/g,d.details.name)
.replace(/{{SNAP_PATH}}/g,s.dir.streams+d.ke+'/'+d.id+'/s.jpg') .replace(/{{SNAP_PATH}}/g,s.dir.streams+d.ke+'/'+d.id+'/s.jpg')

View File

@ -428,7 +428,7 @@ module.exports = (s,config,lang) => {
} }
if(!videoCodecisCopy || outputRequiresEncoding){ if(!videoCodecisCopy || outputRequiresEncoding){
if(videoWidth && videoHeight)streamFlags.push(`-s ${videoWidth}x${videoHeight}`) 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}`) streamFilters.push(`fps=${videoFps}`)
} }
} }
@ -495,7 +495,7 @@ module.exports = (s,config,lang) => {
const isCudaEnabled = hasCudaEnabled(e) const isCudaEnabled = hasCudaEnabled(e)
const videoFlags = [] const videoFlags = []
const videoFilters = [] 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) const { videoWidth, videoHeight } = validateDimensions(e.details.snap_scale_x,e.details.snap_scale_y)
if(inputMap)videoFlags.push(inputMap) if(inputMap)videoFlags.push(inputMap)
if(e.details.snap_vf)videoFilters.push(e.details.snap_vf) if(e.details.snap_vf)videoFilters.push(e.details.snap_vf)

View File

@ -196,7 +196,7 @@ module.exports = function(s,config,lang){
var iconImageFile = streamDir + 'icon.jpg' var iconImageFile = streamDir + 'icon.jpg'
const snapRawFilters = monitor.details.cust_snap_raw const snapRawFilters = monitor.details.cust_snap_raw
if(snapRawFilters)outputOptions.push(snapRawFilters); 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{ try{
await fs.promises.mkdir(streamDir, {recursive: true}, (err) => {s.debugLog(err)}) await fs.promises.mkdir(streamDir, {recursive: true}, (err) => {s.debugLog(err)})
}catch(err){ }catch(err){
@ -796,6 +796,7 @@ module.exports = function(s,config,lang){
[ [
'auth_socket', 'auth_socket',
'get_monitors', 'get_monitors',
'edit_monitors',
'control_monitors', 'control_monitors',
'get_logs', 'get_logs',
'watch_stream', 'watch_stream',

View File

@ -1,3 +1,4 @@
module.exports = (s,config,lang) => {
const fs = require('fs'); const fs = require('fs');
const URL = require('url'); const URL = require('url');
const events = require('events'); const events = require('events');
@ -9,7 +10,6 @@ const connectionTester = require('connection-tester')
const SoundDetection = require('shinobi-sound-detection') const SoundDetection = require('shinobi-sound-detection')
const streamViewerCountTimeouts = {} const streamViewerCountTimeouts = {}
const { createQueueAwaited } = require('../common.js') const { createQueueAwaited } = require('../common.js')
module.exports = (s,config,lang) => {
const { const {
applyPartialToConfiguration, applyPartialToConfiguration,
getWarningChangesForMonitor, getWarningChangesForMonitor,
@ -29,6 +29,7 @@ module.exports = (s,config,lang) => {
} = require('../events/utils.js')(s,config,lang) } = require('../events/utils.js')(s,config,lang)
const { const {
setHomePositionPreset, setHomePositionPreset,
moveToHomePosition,
} = require('../control/ptz.js')(s,config,lang) } = require('../control/ptz.js')(s,config,lang)
const { const {
scanForOrphanedVideos, scanForOrphanedVideos,
@ -498,7 +499,20 @@ module.exports = (s,config,lang) => {
const groupKey = e.ke const groupKey = e.ke
const monitorId = e.mid || e.id const monitorId = e.mid || e.id
const activeMonitor = getActiveMonitor(groupKey,monitorId); 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(){ activeMonitor.spawn_exit = async function(){
clearTimeout(initialHeartBeat)
if(activeMonitor.isStarted === true){ if(activeMonitor.isStarted === true){
if(e.details.loglevel !== 'quiet'){ if(e.details.loglevel !== 'quiet'){
s.userLog(e,{type:lang['Process Unexpected Exit'],msg:{msg:lang.unexpectedExitText,cmd:activeMonitor.ffmpeg}}); 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, forceCheck: true,
checkMax: 2 checkMax: 2
}) })
const monitorConfig = copyMonitorConfiguration(groupKey,monitorId);
s.onMonitorUnexpectedExitExtensions.forEach(function(extender){ s.onMonitorUnexpectedExitExtensions.forEach(function(extender){
extender(monitorConfig,e) extender(monitorConfig,e)
}) })
@ -1707,7 +1720,8 @@ module.exports = (s,config,lang) => {
activeMonitor.errorFatalCount = 0; activeMonitor.errorFatalCount = 0;
delete(activeMonitor.childNode) delete(activeMonitor.childNode)
if(e.details.detector_ptz_follow === '1'){ if(e.details.detector_ptz_follow === '1'){
setHomePositionPreset(e) // setHomePositionPreset(e)
moveToHomePosition(e)
} }
try{ try{
await launchMonitorProcesses(e) await launchMonitorProcesses(e)
@ -1778,6 +1792,36 @@ module.exports = (s,config,lang) => {
const streamDir = s.dir.streams + options.ke + '/' + options.mid + '/' const streamDir = s.dir.streams + options.ke + '/' + options.mid + '/'
return streamDir 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 { return {
monitorStop, monitorStop,
monitorIdle, monitorIdle,
@ -1809,5 +1853,6 @@ module.exports = (s,config,lang) => {
getActiveViewerCount: getActiveViewerCount, getActiveViewerCount: getActiveViewerCount,
setTimedActiveViewerForHttp: setTimedActiveViewerForHttp, setTimedActiveViewerForHttp: setTimedActiveViewerForHttp,
attachMainProcessHandlers: attachMainProcessHandlers, attachMainProcessHandlers: attachMainProcessHandlers,
removeSenstiveInfoFromMonitorConfig,
} }
} }

View File

@ -18,11 +18,11 @@ module.exports = function(s,config,lang){
){ ){
require('./notifications/email.js')(s,config,lang,getSnapshot) 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/emailByUser.js')(s,config,lang,getSnapshot)
require('./notifications/discordBot.js')(s,config,lang,getSnapshot) require('./notifications/discordBot.js')(s,config,lang,getSnapshot)
require('./notifications/telegram.js')(s,config,lang,getSnapshot) require('./notifications/telegram.js')(s,config,lang,getSnapshot)
require('./notifications/pushover.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/mqtt.js')(s,config,lang,getSnapshot)
require('./notifications/matrix.js')(s,config,lang,getSnapshot) require('./notifications/matrix.js')(s,config,lang,getSnapshot)
} }

View File

@ -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 includeSnapshot = s.group[groupKey].init.global_webhook_include_image === '1';
const webhookInfoData = { const webhookInfoData = {
info: sendBody, info: sendBody,
files: [], files: [],
} }
if(files){
const formData = new FormData(); const formData = new FormData();
if(files && doPostMethod){
files.forEach(async (file,n) => { files.forEach(async (file,n) => {
const fileName = file.name
switch(file.type){ switch(file.type){
case'video': case'video':
// video cannot be sent this way unless POST // video cannot be sent this way unless POST
if(doPostMethod){
const fileName = file.name
formData.append(`file${n + 1}`, file.attachment, { formData.append(`file${n + 1}`, file.attachment, {
contentType: 'video/mp4', contentType: 'video/mp4',
name: fileName, name: fileName,
filename: fileName, filename: fileName,
}); });
webhookInfoData.files.push(fileName) webhookInfoData.files.push(fileName)
}
break; break;
case'photo': case'photo':
if(doPostMethod){
const fileName = file.name
formData.append(`file${n + 1}`, file.attachment, { formData.append(`file${n + 1}`, file.attachment, {
contentType: 'image/jpeg', contentType: 'image/jpeg',
name: fileName, name: fileName,
filename: fileName, filename: fileName,
}); });
webhookInfoData.files.push(fileName) webhookInfoData.files.push(fileName)
}else{
const base64StringofImage = file.attachment.toString('base64')
webhookInfoData.files.push(base64StringofImage)
}
break; break;
} }
}) })

View File

@ -1,16 +1,34 @@
module.exports = function(s,config,lang,app,io){ module.exports = function(s,config,lang,app,io){
const {
scanStatus,
runOnvifScanner,
stopOnvifScanner,
} = require('./scanners/utils.js')(s,config,lang)
const { const {
ffprobe, ffprobe,
} = require('./ffmpeg/utils.js')(s,config,lang) } = require('./ffmpeg/utils.js')(s,config,lang)
const {
runOnvifScanner,
} = require('./scanners/utils.js')(s,config,lang)
const onWebSocketConnection = async (cn) => { const onWebSocketConnection = async (cn) => {
const tx = function(z){if(!z.ke){z.ke=cn.ke;};cn.emit('f',z);} const tx = function(z){if(!z.ke){z.ke=cn.ke;};cn.emit('f',z);}
cn.on('f',(d) => { cn.on('f',(d) => {
switch(d.f){ 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': 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; break;
} }
}) })
@ -40,4 +58,60 @@ module.exports = function(s,config,lang,app,io){
}) })
},res,req); },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);
})
} }

View File

@ -1,202 +1,377 @@
var os = require('os'); var os = require('os');
const async = require('async');
const onvif = require("shinobi-onvif"); const onvif = require("shinobi-onvif");
const { const {
addCredentialsToUrl, addCredentialsToUrl,
stringContains, stringContains,
getBuffer, getBuffer,
} = require('../common.js') } = require('../common.js')
const scanStatus = {
current: [],
allSuccessful: {},
cancelPromises: null,
abortController: null
};
module.exports = (s,config,lang) => { module.exports = (s,config,lang) => {
const ipRange = (start_ip, end_ip) => { const ipRange = (start_ip, end_ip) => {
var startLong = toLong(start_ip); const startLong = toLong(start_ip);
var endLong = toLong(end_ip); const endLong = toLong(end_ip);
if (startLong > endLong) { if (startLong > endLong) {
var tmp = startLong; const tmp = startLong;
startLong = endLong startLong = endLong;
endLong = tmp; endLong = tmp;
} }
var rangeArray = []; const rangeArray = [];
var i; for (let i = startLong; i <= endLong; i++) {
for (i = startLong; i <= endLong; i++) {
rangeArray.push(fromLong(i)); rangeArray.push(fromLong(i));
} }
return rangeArray; return rangeArray;
} };
const portRange = (lowEnd, highEnd) => { const portRange = (lowEnd, highEnd) => {
var list = []; const list = [];
for (var i = lowEnd; i <= highEnd; i++) { for (let i = lowEnd; i <= highEnd; i++) {
list.push(i); list.push(i);
} }
return list; return list;
} };
//toLong taken from NPM package 'ip'
const toLong = (ip) => { const toLong = (ip) => {
var ipl = 0; let ipl = 0;
ip.split('.').forEach(function(octet) { ip.split('.').forEach(function(octet) {
ipl <<= 8; ipl <<= 8;
ipl += parseInt(octet); ipl += parseInt(octet);
}); });
return (ipl >>> 0); return (ipl >>> 0);
} };
//fromLong taken from NPM package 'ip'
const fromLong = (ipl) => { const fromLong = (ipl) => {
return ((ipl >>> 24) + '.' + return ((ipl >>> 24) + '.' +
(ipl >> 16 & 255) + '.' + (ipl >> 16 & 255) + '.' +
(ipl >> 8 & 255) + '.' + (ipl >> 8 & 255) + '.' +
(ipl & 255)); (ipl & 255));
} };
const runOnvifScanner = (options,foundCameraCallback) => {
var ip = options.ip.replace(/ /g,'') const getNetworkAddresses = () => {
var ports = options.port.replace(/ /g,'') const interfaces = os.networkInterfaces();
if(options.ip === ''){ const addresses = [];
var interfaces = os.networkInterfaces() for (const k in interfaces) {
var addresses = [] for (const k2 in interfaces[k]) {
for (var k in interfaces) { const address = interfaces[k][k2];
for (var k2 in interfaces[k]) {
var address = interfaces[k][k2]
if (address.family === 'IPv4' && !address.internal) { if (address.family === 'IPv4' && !address.internal) {
addresses.push(address.address) addresses.push(address.address);
} }
} }
} }
const addressRange = [] return addresses;
addresses.forEach(function(address){ };
if(address.indexOf('0.0.0')>-1){return false}
var addressPrefix = address.split('.') const getAddressRange = (addresses) => {
delete(addressPrefix[3]); const addressRange = [];
addressPrefix = addressPrefix.join('.') addresses.forEach((address) => {
addressRange.push(`${addressPrefix}1-${addressPrefix}254`) if (address.indexOf('0.0.0') > -1) return;
}) const addressPrefix = address.split('.').slice(0, 3).join('.');
ip = addressRange.join(',') addressRange.push(`${addressPrefix}.1-${addressPrefix}.254`);
} });
return addressRange.join(',');
};
const getPorts = (ports) => {
if (ports === '') { if (ports === '') {
ports = '80,8080,8000,7575,8081,9080,8090,8999,8899' return '80,8080,8000,7575,8081,9080,8090,8999,8899'.split(',');
} }
if (ports.indexOf('-') > -1) { if (ports.indexOf('-') > -1) {
ports = ports.split('-') const [start, end] = ports.split('-');
var portRangeStart = ports[0] return portRange(start, end);
var portRangeEnd = ports[1]
ports = portRange(portRangeStart,portRangeEnd);
}else{
ports = ports.split(',')
} }
var ipList = options.ipList return ports.split(',');
var onvifUsername = options.user || '' };
var onvifPassword = options.pass || ''
ip.split(',').forEach(function(addressRange){ const getIpList = (ip) => {
var ipRangeStart = addressRange[0] const ipList = [];
var ipRangeEnd = addressRange[1] ip.split(',').forEach((range) => {
if(addressRange.indexOf('-')>-1){ const [start, end] = range.indexOf('-') > -1 ? range.split('-') : [range, range];
addressRange = addressRange.split('-'); ipList.push(...ipRange(start, end));
ipRangeStart = addressRange[0] });
ipRangeEnd = addressRange[1] return ipList;
}else{ };
ipRangeStart = addressRange
ipRangeEnd = addressRange const createHitList = (ipList, ports, onvifUsername = '', onvifPassword = '') => {
} const hitList = [];
if(!ipList){ const usernameVariants = onvifUsername.split(',');
ipList = ipRange(ipRangeStart,ipRangeEnd); const passwordVariants = onvifPassword.split(',');
}else{ for (const username of usernameVariants) {
ipList = ipList.concat(ipRange(ipRangeStart,ipRangeEnd)) for (const password of passwordVariants) {
} hitList.push(...ipList.flatMap((ipEntry) =>
}) ports.map((portEntry) => ({
var hitList = [] xaddr: `http://${ipEntry}:${portEntry}/onvif/device_service`,
ipList.forEach((ipEntry,n) => { user: username,
ports.forEach((portEntry,nn) => { pass: password,
hitList.push({
xaddr : 'http://' + ipEntry + ':' + portEntry + '/onvif/device_service',
user : onvifUsername,
pass : onvifPassword,
ip: ipEntry, ip: ipEntry,
port: portEntry, port: portEntry,
}) }))
}) ));
}) }
var responseList = [] }
hitList.forEach(async (camera) => { return hitList;
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 = { const takeSnapshot = async (cameraResponse, device) => {
ip: camera.ip,
port: camera.port,
info: info,
date: date,
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){
s.debugLog(err)
}
responseList.push(cameraResponse)
var imageSnap
try { try {
const snapUri = addCredentialsToUrl({ const snapUri = addCredentialsToUrl({
username: onvifUsername, username: cameraResponse.user,
password: onvifPassword, password: cameraResponse.pass,
url: (await device.services.media.getSnapshotUri({ url: (await device.services.media.getSnapshotUri({ ProfileToken: device.current_profile.token })).data.GetSnapshotUriResponse.MediaUri.Uri,
ProfileToken : device.current_profile.token,
})).data.GetSnapshotUriResponse.MediaUri.Uri,
}); });
imageSnap = (await getBuffer(snapUri)).toString('base64'); const imgBuffer = await getBuffer(snapUri);
cameraResponse.snapShot = imgBuffer.toString('base64');
} catch (err) { } catch (err) {
s.debugLog(err) console.error('Failed to get snapshot via ONVIF:', err);
} }
if(foundCameraCallback)foundCameraCallback(Object.assign(cameraResponse,{f: 'onvif', snapShot: imageSnap})) return cameraResponse;
}catch(err){
const searchError = (find) => {
return stringContains(find,err.message,true)
} }
var foundDevice = false
var errorMessage = '' const fetchCameraDetails = async (camera, onvifUsername, onvifPassword, foundCameraCallback, failedCameraCallback) => {
switch(true){ const previousSuccess = scanStatus.allSuccessful[camera.ip];
//ONVIF camera found but denied access if (previousSuccess) {
case searchError('400'): //Bad Request - Sender not Authorized // console.log('FOUND PREVIOUS', camera.ip);
foundDevice = true foundCameraCallback(previousSuccess);
errorMessage = lang.ONVIFErr400 return;
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){ try {
foundCameraCallback({ const device = new onvif.OnvifDevice(camera);
f: 'onvif', const info = await device.init();
ff: 'failed_capture', const stream = await device.services.media.getStreamUri({
ProfileToken: device.current_profile.token,
Protocol: 'RTSP'
});
const cameraResponse = {
ip: camera.ip, ip: camera.ip,
port: camera.port, port: camera.port,
error: errorMessage 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(config.debugLogVerbose)s.debugLog(err); 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});
return responseList
} }
}
}, 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 { return {
ipRange: ipRange, expandIPRange,
portRange: portRange, ipRange,
runOnvifScanner: runOnvifScanner, portRange,
} scanStatus,
runOnvifScanner,
stopOnvifScanner,
};
} }

View File

@ -33,7 +33,7 @@ module.exports = (config) => {
}, },
modifyConfiguration: (postBody) => { modifyConfiguration: (postBody) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.log(s.location.config) console.log(config)
const configPath = config.thisIsDocker ? "/config/conf.json" : s.location.config; const configPath = config.thisIsDocker ? "/config/conf.json" : s.location.config;
const configData = JSON.stringify(postBody,null,3); const configData = JSON.stringify(postBody,null,3);

View File

@ -66,10 +66,17 @@ module.exports = (s,config,lang) => {
const videosDirectory = s.getVideoDirectory(monitor) const videosDirectory = s.getVideoDirectory(monitor)
const tempDirectory = s.getStreamsDirectory(monitor) const tempDirectory = s.getStreamsDirectory(monitor)
// const findCmd = [videosDirectory].concat(options.flags || ['-maxdepth','1']) // const findCmd = [videosDirectory].concat(options.flags || ['-maxdepth','1'])
try{
fs.writeFileSync( fs.writeFileSync(
tempDirectory + 'orphanCheck.sh', tempDirectory + 'orphanCheck.sh',
`find "${s.checkCorrectPathEnding(videosDirectory,true)}" -maxdepth 1 -type f -exec stat -c "%n" {} + | sort -r | head -n ${options.checkMax}` `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']) let listing = spawn('sh',[tempDirectory + 'orphanCheck.sh'])
// const onData = options.onData ? options.onData : () => {} // const onData = options.onData ? options.onData : () => {}
const onError = options.onError ? options.onError : s.systemLog const onError = options.onError ? options.onError : s.systemLog

View File

@ -54,6 +54,10 @@ module.exports = function(s,config,lang,io){
if(config.renderPaths.grid === undefined){config.renderPaths.grid='pages/grid'} if(config.renderPaths.grid === undefined){config.renderPaths.grid='pages/grid'}
//slick.js (cycle) page //slick.js (cycle) page
if(config.renderPaths.cycle === undefined){config.renderPaths.cycle='pages/cycle'} 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 // Use uws/cws
if(config.useUWebsocketJs === undefined){config.useUWebsocketJs=true} if(config.useUWebsocketJs === undefined){config.useUWebsocketJs=true}
if(config.webBlocksPreloaded === undefined){ if(config.webBlocksPreloaded === undefined){

View File

@ -311,7 +311,7 @@ module.exports = function(s,config,lang,app){
} = s.checkPermission(user) } = s.checkPermission(user)
if( if(
userPermissions.monitor_create_disallowed || userPermissions.monitor_create_disallowed ||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed || isRestrictedApiKey && apiKeyPermissions.edit_monitors_disallowed ||
isRestricted && !monitorPermissions[`${monitorId}_monitor_edit`] isRestricted && !monitorPermissions[`${monitorId}_monitor_edit`]
){ ){
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']}); s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});

View File

@ -31,6 +31,7 @@ module.exports = function(s,config,lang,app,io){
const { const {
spawnSubstreamProcess, spawnSubstreamProcess,
destroySubstreamProcess, destroySubstreamProcess,
removeSenstiveInfoFromMonitorConfig,
} = require('./monitor/utils.js')(s,config,lang) } = require('./monitor/utils.js')(s,config,lang)
const { const {
sliceVideo, sliceVideo,
@ -771,6 +772,7 @@ module.exports = function(s,config,lang,app,io){
} = s.getMonitorsPermitted(user.details,monitorId) } = s.getMonitorsPermitted(user.details,monitorId)
const { const {
isRestricted, isRestricted,
userPermissions,
isRestrictedApiKey, isRestrictedApiKey,
apiKeyPermissions, apiKeyPermissions,
} = s.checkPermission(user) } = s.checkPermission(user)
@ -784,6 +786,7 @@ module.exports = function(s,config,lang,app,io){
s.closeJsonResponse(res,[]); s.closeJsonResponse(res,[]);
return return
} }
const cannotSeeImportantSettings = (isRestrictedApiKey && apiKeyPermissions.edit_monitors_disallowed) || userPermissions.monitor_create_disallowed;
s.knexQuery({ s.knexQuery({
action: "select", action: "select",
columns: "*", columns: "*",
@ -794,6 +797,12 @@ module.exports = function(s,config,lang,app,io){
] ]
},(err,r) => { },(err,r) => {
r.forEach(function(v,n){ 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]){ if(s.group[v.ke] && s.group[v.ke].activeMonitors[v.mid]){
const activeMonitor = s.group[v.ke].activeMonitors[v.mid] const activeMonitor = s.group[v.ke].activeMonitors[v.mid]
r[n].currentlyWatching = Object.keys(activeMonitor.watch).length r[n].currentlyWatching = Object.keys(activeMonitor.watch).length
@ -847,7 +856,6 @@ module.exports = function(s,config,lang,app,io){
} }
return streamURL 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.tv_channel_id||details.tv_channel_id==='')details.tv_channel_id = 'temp_'+s.gid(5)
if(details.snap==='1'){ if(details.snap==='1'){
r[n].snapshot = '/'+req.params.auth+'/jpeg/'+v.ke+'/'+v.mid+'/s.jpg' 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); },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 * API : Get Events
*/ */
app.get([ app.get([

View File

@ -415,4 +415,33 @@ module.exports = function(s,config,lang,app){
},res,req); },res,req);
},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);
});
} }

3952
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -33,20 +33,20 @@
"googleapis": "^100.0.0", "googleapis": "^100.0.0",
"http-proxy": "^1.18.1", "http-proxy": "^1.18.1",
"jsonfile": "^3.0.1", "jsonfile": "^3.0.1",
"knex": "^0.21.21", "knex": "^3.1.0",
"ldapauth-fork": "^5.0.2", "ldapauth-fork": "^5.0.2",
"marked": "^4.3.0", "marked": "^4.3.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"mp4frag": "^0.6.1", "mp4frag": "^0.6.1",
"mqtt": "^4.3.7", "mqtt": "^4.3.7",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"mysql2": "^2.1.0", "mysql2": "^3.9.7",
"node-abort-controller": "^3.0.1", "node-abort-controller": "^3.0.1",
"node-fetch": "^2.6.7", "node-fetch": "^2.6.7",
"node-onvif-events": "^2.0.5",
"node-ssh": "^12.0.4", "node-ssh": "^12.0.4",
"node-telegram-bot-api": "^0.61.0", "node-telegram-bot-api": "^0.65.1",
"nodemailer": "^6.7.1", "nodemailer": "^6.7.1",
"onvif": "^0.7.1",
"pam-diff": "^1.1.0", "pam-diff": "^1.1.0",
"path": "^0.12.7", "path": "^0.12.7",
"pipe2pam": "^0.6.2", "pipe2pam": "^0.6.2",
@ -54,7 +54,7 @@
"pushover-notifications": "^1.2.2", "pushover-notifications": "^1.2.2",
"sat": "^0.7.1", "sat": "^0.7.1",
"shinobi-node-moving-things-tracker": "^0.9.3", "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-sound-detection": "^0.1.13",
"shinobi-zwave": "^1.0.11", "shinobi-zwave": "^1.0.11",
"smtp-server": "^3.13.0", "smtp-server": "^3.13.0",

68
tools/downloadPlugins.js Normal file
View File

@ -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);
}

View File

@ -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();

546
tools/onvifUtilsForTest.js Normal file
View File

@ -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,
}

View File

@ -3,12 +3,22 @@
background-size: cover; background-size: cover;
height: 100px; height: 100px;
} }
#tab-onvifScanner .onvif_result .card { /* #tab-onvifScanner .onvif_result .card {
cursor: pointer; cursor: pointer;
} } */
#tab-onvifScanner .onvif_result .card-body { #tab-onvifScanner .onvif_result .card-body {
min-height: auto; min-height: auto;
} }
#tab-onvifScanner .onvif_result { #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;
} }

View File

@ -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;
}

View File

@ -35,6 +35,12 @@
#easyRemoteAccess .card.active .fa { #easyRemoteAccess .card.active .fa {
color: #6ee068; color: #6ee068;
} }
#easyRemoteAccess .card .remote-dashboard-link {
display: none!important;
}
#easyRemoteAccess .card.active .remote-dashboard-link {
display: block!important;
}
#easyRemoteAccess .card .selected-badge { #easyRemoteAccess .card .selected-badge {
display: none; display: none;
color: #fff!important; color: #fff!important;
@ -56,3 +62,7 @@
#p2pServerList .card .d-flex.flex-row:nth-of-type(odd) { #p2pServerList .card .d-flex.flex-row:nth-of-type(odd) {
background: rgba(0,0,0,0.1) background: rgba(0,0,0,0.1)
} }
#easyRemoteAccess .active .activate-remote-selection .fa-check:before {
content: "\f00d"!important;
}

View File

@ -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> &nbsp;
<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
})

View File

@ -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));
}
}
})

View File

@ -239,7 +239,7 @@ function getApiHost(path,isAdmin){
return getLocation() + (window.adminApiPrefix && isAdmin ? `${window.adminApiPrefix}` : ''); return getLocation() + (window.adminApiPrefix && isAdmin ? `${window.adminApiPrefix}` : '');
} }
function getApiPrefix(path,isAdmin){ 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 return path ? mainPart + '/' + path + '/' + $user.ke : mainPart
} }
@ -1096,183 +1096,9 @@ function featureIsActivated(showNotice){
return false return false
} }
} }
$(document).ready(function(){ function makeButton({ color, link, text, class: classes}){
onInitWebsocket(function(){ return `<a class="btn btn-sm d-block btn-${color} ${classes}" ${link ? `href="${link}" target="_blank"` : ''}>${text}</a>`
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() function replaceBrokenImage(_this){
return false; $(_this).attr('src', `${libURL}/libs/img/bg.jpg`)
})
.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));
}
}
})

View File

@ -195,15 +195,40 @@ function initiateLiveGridPlayer(monitor,subStreamChannel){
var stream = containerElement.find('.stream-element'); var stream = containerElement.find('.stream-element');
var onPoseidonError = function(){ var onPoseidonError = function(){
// setTimeout(function(){ // setTimeout(function(){
// mainSocket.f({f:'monitor',ff:'watch_on',id:monitor.mid}) // mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId})
// },5000) // },2000)
} }
if(!loadedPlayer.PoseidonErrorCount)loadedPlayer.PoseidonErrorCount = 0 if(!loadedPlayer.PoseidonErrorCount)loadedPlayer.PoseidonErrorCount = 0
if(loadedPlayer.PoseidonErrorCount >= 5)return if(loadedPlayer.PoseidonErrorCount >= 5)return
if(subStreamChannel ? details.substream.output.stream_flv_type === 'ws' : monitor.details.stream_flv_type === 'ws'){
if(loadedPlayer.Poseidon){
loadedPlayer.Poseidon.stop()
revokeVideoPlayerUrl(monitorId)
}
try{
loadedPlayer.Poseidon = new Poseidon({
video: stream[0],
auth_token: $user.auth_token,
ke: monitor.ke,
uid: $user.uid,
id: monitor.mid,
url: location.origin,
path: websocketPath,
query: websocketQuery,
onError : onPoseidonError,
channel : subStreamChannel
})
loadedPlayer.Poseidon.start();
}catch(err){
// onPoseidonError()
console.log('onTryPoseidonError',err)
}
}else{
stream.attr('src',getApiPrefix(`mp4`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.mp4?time=' + (new Date()).getTime()) stream.attr('src',getApiPrefix(`mp4`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.mp4?time=' + (new Date()).getTime())
stream[0].onerror = function(err){ stream[0].onerror = function(err){
console.error(err) console.error(err)
} }
}
},1000) },1000)
break; break;
case'flv': case'flv':

View File

@ -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;
})
})

View File

@ -136,6 +136,7 @@ function resetMonitorCanvas(monitorId,initiateAfter,subStreamChannel){
streamBlock.append(buildStreamElementHtml(streamType)) streamBlock.append(buildStreamElementHtml(streamType))
attachVideoElementErrorHandler(monitorId) attachVideoElementErrorHandler(monitorId)
if(initiateAfter)initiateLiveGridPlayer(monitor,subStreamChannel) if(initiateAfter)initiateLiveGridPlayer(monitor,subStreamChannel)
resetLiveGridDimensionsInMemory(monitorId)
} }
function replaceMonitorInfoInHtml(htmlString,monitor){ function replaceMonitorInfoInHtml(htmlString,monitor){
var monitorMutes = dashboardOptions().monitorMutes || {} var monitorMutes = dashboardOptions().monitorMutes || {}
@ -261,6 +262,7 @@ function loadVideoMiniList(monitorId){
} }
function updateLiveGridElementHeightWidth(monitorId){ function updateLiveGridElementHeightWidth(monitorId){
var liveGridElement = liveGridElements[monitorId] var liveGridElement = liveGridElements[monitorId]
liveGridElement.streamElement = liveGridElement.monitorItem.find('.stream-element')
var streamElement = liveGridElement.streamElement var streamElement = liveGridElement.streamElement
liveGridElement.width = streamElement.width() liveGridElement.width = streamElement.width()
liveGridElement.height = streamElement.height() liveGridElement.height = streamElement.height()
@ -712,12 +714,19 @@ function openLiveGrid(){
} }
} }
function popOutMonitor(monitorId){ function popOutMonitor(monitorId){
var monitorPop = monitorPops[monitorId] var monitorPop = monitorPops[monitorId] || {}
function finish(img){ if(monitorPop.isOpen){
if(monitorPop){ return
monitorPop.close() }
function finish(img){
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]){ if(loadedLiveGrids[monitorId]){
getSnapshot(loadedMonitors[monitorId],function(url){ getSnapshot(loadedMonitors[monitorId],function(url){
@ -736,6 +745,12 @@ function popOutMonitor(monitorId){
finish(img) 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){ function fullScreenLiveGridStream(monitorItem){
var videoElement = monitorItem.find('.stream-element') var videoElement = monitorItem.find('.stream-element')
monitorItem.addClass('fullscreen') monitorItem.addClass('fullscreen')
@ -1092,6 +1107,9 @@ $(document).ready(function(e){
var monitorId = $(this).parents('[data-mid]').attr('data-mid') var monitorId = $(this).parents('[data-mid]').attr('data-mid')
popOutMonitor(monitorId) popOutMonitor(monitorId)
}) })
.on('click','.open-wallview',function(){
createWallViewWindow()
})
.on('click','.toggle-monitor-substream',function(){ .on('click','.toggle-monitor-substream',function(){
var monitorId = $(this).parents('[data-mid]').attr('data-mid') var monitorId = $(this).parents('[data-mid]').attr('data-mid')
toggleSubStream(monitorId) toggleSubStream(monitorId)
@ -1290,6 +1308,7 @@ $(document).ready(function(e){
// break; // break;
case'detector_trigger': case'detector_trigger':
var monitorId = d.id var monitorId = d.id
var matrices = d.details.matrices
var liveGridElement = liveGridElements[monitorId] var liveGridElement = liveGridElements[monitorId]
if(!window.dontShowDetection && liveGridElement){ if(!window.dontShowDetection && liveGridElement){
var monitorElement = liveGridElement.monitorItem var monitorElement = liveGridElement.monitorItem
@ -1303,12 +1322,12 @@ $(document).ready(function(e){
}else{ }else{
monitorElement.removeClass('doObjectDetection') monitorElement.removeClass('doObjectDetection')
} }
if(d.details.matrices&&d.details.matrices.length>0){ if(matrices && matrices.length > 0){
drawMatrices(d,{ drawMatrices(d,{
theContainer: liveGridElement.eventObjects, theContainer: liveGridElement.eventObjects,
height: liveGridElement.height, height: liveGridElement.height,
width: liveGridElement.width, width: liveGridElement.width,
}) }, null, true)
} }
if(d.details.confidence){ if(d.details.confidence){
var eventConfidence = d.details.confidence var eventConfidence = d.details.confidence
@ -1329,7 +1348,7 @@ $(document).ready(function(e){
} }
playAudioAlert() playAudioAlert()
var monitorPop = monitorPops[monitorId] var monitorPop = monitorPops[monitorId]
if(window.popLiveOnEvent && (!monitorPop || monitorPop.closed === true)){ if(window.popLiveOnEvent && (!monitorPop || !monitorPop.isOpen)){
popOutMonitor(monitorId) popOutMonitor(monitorId)
} }
// console.log({ // console.log({

View File

@ -1150,8 +1150,8 @@ editorForm.find('[name="type"]').change(function(e){
setCosmeticMonitorInfo(newMonitorData) setCosmeticMonitorInfo(newMonitorData)
drawMonitorGroupList() drawMonitorGroupList()
if(!d.silenceNote){ if(!d.silenceNote){
new PNotify({ redAlertNotify({
title: 'Monitor Saved', title: lang['Monitor Saved'],
text: '<b>'+newMonitorData.name+'</b> <small>'+newMonitorData.mid+'</small> has been saved.', text: '<b>'+newMonitorData.name+'</b> <small>'+newMonitorData.mid+'</small> has been saved.',
type: 'success' type: 'success'
}) })

View File

@ -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){ function buildPosePoints(bodyParts, x, y){
let theArray = [] let theArray = []
for(const point of bodyParts){ for(const point of bodyParts){
@ -683,7 +702,7 @@ function buildPosePoints(bodyParts, x, y){
} }
return theArray; return theArray;
} }
function drawMatrices(event,options){ function drawMatrices(event, options, autoRemoveTimeout, drawTrails){
var theContainer = options.theContainer var theContainer = options.theContainer
var height = options.height var height = options.height
var width = options.width var width = options.width
@ -695,8 +714,11 @@ function drawMatrices(event,options){
let moreMatrices = [] let moreMatrices = []
var monitorId = event.id; var monitorId = event.id;
function processMatrix(n,matrix){ 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};">` const newWidth = widthRatio * matrix.width;
if(matrix.tag)html += `<span class="tag">${matrix.tag}${!isNaN(matrix.id) ? ` <small class="label label-default">${matrix.id}</small>`: ''}</span>` 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.notice)html += `<div class="matrix-info" style="color:yellow">${matrix.notice}</div>`;
if(matrix.missingNear && matrix.missingNear.length > 0){ 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>`; 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){ if(matrix.nearBy){
html += `<div class="matrix-info">` html += `<div class="matrix-info">`
matrix.nearBy.forEach((nearMatrix) => { 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>` html += `</div>`
} }
@ -740,7 +762,19 @@ function drawMatrices(event,options){
} }
$.each(event.details.matrices, processMatrix); $.each(event.details.matrices, processMatrix);
$.each(moreMatrices, 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(){ function setMonitorCountOnUI(){
$('.cameraCount').text(Object.keys(loadedMonitors).length) $('.cameraCount').text(Object.keys(loadedMonitors).length)

View File

@ -4,6 +4,8 @@ $(document).ready(function(e){
var loadedResultsByIp = {} var loadedResultsByIp = {}
var monitorEditorWindow = $('#tab-monitorSettings') var monitorEditorWindow = $('#tab-monitorSettings')
var onvifScannerWindow = $('#tab-onvifScanner') var onvifScannerWindow = $('#tab-onvifScanner')
var onvifScannerStartButton = onvifScannerWindow.find('.start-scan')
var onvifScannerStopButton = onvifScannerWindow.find('.stop-scan')
var onvifScannerResultPane = onvifScannerWindow.find('.onvif_result') var onvifScannerResultPane = onvifScannerWindow.find('.onvif_result')
var onvifScannerErrorResultPane = onvifScannerWindow.find('.onvif_result_error') var onvifScannerErrorResultPane = onvifScannerWindow.find('.onvif_result_error')
var scanForm = onvifScannerWindow.find('form'); var scanForm = onvifScannerWindow.find('form');
@ -30,21 +32,60 @@ $(document).ready(function(e){
var html = buildSubMenuItems(allFound) var html = buildSubMenuItems(allFound)
sideMenuList.html(html) sideMenuList.html(html)
} }
var setAsLoading = function(appearance){ var showStopButton = function(appearance){
if(appearance){ if(appearance){
onvifScannerWindow.find('._loading').show() onvifScannerStartButton.addClass('d-none')
onvifScannerWindow.find('[type="submit"]').prop('disabled',true) onvifScannerStopButton.removeClass('d-none')
}else{ }else{
onvifScannerWindow.find('._loading').hide() onvifScannerStartButton.removeClass('d-none')
onvifScannerWindow.find('[type="submit"]').prop('disabled',false) onvifScannerStopButton.addClass('d-none')
} }
} }
function drawProbeResult(options){
if(!options.error){ function drawDeviceTableRow(device, gotAccess){
var currentUsername = onvifScannerWindow.find('[name="user"]').val() var ip = device.ip;
var currentPassword = onvifScannerWindow.find('[name="pass"]').val() var el = onvifScannerResultPane.find(`[scan-item="${ip}"]`)
var tempID = generateId() var hasError = !!device.error;
var info = options.info ? jsonToHtmlBlock(options.info) : '' 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{
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 streamUrl = ''
var launchWebPage = `target="_blank" href="http${options.port == 443 ? 's' : ''}://${options.ip}:${options.port}"` var launchWebPage = `target="_blank" href="http${options.port == 443 ? 's' : ''}://${options.ip}:${options.port}"`
if(options.uri){ if(options.uri){
@ -54,7 +95,7 @@ $(document).ready(function(e){
var pathLocation = theLocation.location var pathLocation = theLocation.location
var monitorConfigPartial = { var monitorConfigPartial = {
name: pathLocation.hostname, name: pathLocation.hostname,
mid: tempID + `${options.port}`, mid: monitorId,
host: pathLocation.hostname, host: pathLocation.hostname,
port: pathLocation.port, port: pathLocation.port,
path: pathLocation.pathname + (pathLocation.search && pathLocation.search !== '?' ? pathLocation.search : ''), path: pathLocation.pathname + (pathLocation.search && pathLocation.search !== '?' ? pathLocation.search : ''),
@ -78,37 +119,9 @@ $(document).ready(function(e){
if(monitorAlreadyAdded){ if(monitorAlreadyAdded){
monitorConfigPartial.mid = monitorAlreadyAdded.mid; monitorConfigPartial.mid = monitorAlreadyAdded.mid;
} }
var monitorId = monitorConfigPartial.mid
loadedResults[monitorId] = monitorConfigPartial; loadedResults[monitorId] = monitorConfigPartial;
loadedResultsByIp[monitorConfigPartial.host] = monitorConfigPartial; loadedResultsByIp[monitorConfigPartial.host] = monitorConfigPartial;
onvifScannerResultPane.append(` return monitorConfigPartial
<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()
}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>
`)
}
}
} }
function isOnvifRowAlreadyALoadedMonitor(onvifRow){ function isOnvifRowAlreadyALoadedMonitor(onvifRow){
var matches = null; var matches = null;
@ -174,7 +187,7 @@ $(document).ready(function(e){
var form = el.serializeObject(); var form = el.serializeObject();
onvifScannerResultPane.empty(); onvifScannerResultPane.empty();
onvifScannerErrorResultPane.empty(); onvifScannerErrorResultPane.empty();
setAsLoading(true) showStopButton(true)
mainSocket.f({ mainSocket.f({
f: 'onvif', f: 'onvif',
ip: form.ip, ip: form.ip,
@ -184,34 +197,80 @@ $(document).ready(function(e){
}); });
clearTimeout(checkTimeout) clearTimeout(checkTimeout)
checkTimeout = setTimeout(function(){ checkTimeout = setTimeout(function(){
if(onvifScannerResultPane.find('.card').length === 0){ if(onvifScannerResultPane.find('[scan-item]').length === 0){
setAsLoading(false) showStopButton(false)
onvifScannerResultPane.append(`<div class="p-2 text-center ${definitions.Theme.isDark ? 'text-white' : ''} _notfound text-white epic-text">${lang.sorryNothingWasFound}</div>`) onvifScannerResultPane.append(`<div class="p-2 text-center ${definitions.Theme.isDark ? 'text-white' : ''} _notfound text-white epic-text">${lang.sorryNothingWasFound}</div>`)
} }
},5000) },5000)
return false; return false;
}); });
onvifScannerWindow.on('click','.copy',function(){ onvifScannerWindow.on('click','.copy',function(e){
e.preventDefault()
openMonitorEditorPage() openMonitorEditorPage()
var el = $(this).parents('[onvif_row]'); var el = $(this).parents('[scan-item]');
var id = el.attr('onvif_row'); var id = el.attr('scan-item');
var onvifRecord = loadedResults[id]; var onvifRecord = loadedResultsByIp[id];
var streamURL = onvifRecord.details.auto_host var streamURL = onvifRecord.details.auto_host
writeToMonitorSettingsWindow(onvifRecord) writeToMonitorSettingsWindow(onvifRecord)
}) })
onvifScannerWindow.on('click','.add-all',function(){ onvifScannerWindow.on('click','.add-all',function(){
filterOutMonitorsThatAreAlreadyAdded(loadedResults,function(importableCameras){ filterOutMonitorsThatAreAlreadyAdded(loadedResults,function(importableCameras){
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){ $.each(importableCameras,function(n,camera){
// console.log(camera) // console.log(camera)
postMonitor(camera) postMonitor(camera)
}) })
}
})
}
}) })
}) })
onvifScannerWindow.on('click','.stop-scan',function(){
mainSocket.f({ f: 'onvif_stop' });
})
loadLocalOptions() loadLocalOptions()
onInitWebsocket(function (){
mainSocket.f({ f: 'onvif_scan_reconnect' });
})
onWebSocketEvent(function (d){ onWebSocketEvent(function (d){
switch(d.f){ switch(d.f){
case'onvif': 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; break;
} }
}) })

View File

@ -8,6 +8,7 @@ $(document).ready(function(){
var timeStripObjectSearchInput = $('#timeline-video-object-search'); var timeStripObjectSearchInput = $('#timeline-video-object-search');
var dateSelector = $('#timeline-date-selector'); var dateSelector = $('#timeline-date-selector');
var sideMenuList = $(`#side-menu-link-timeline ul`) var sideMenuList = $(`#side-menu-link-timeline ul`)
var monitorList = $(`#timeline-monitor-list`)
var playToggles = timeStripControls.find('[timeline-action="playpause"]') var playToggles = timeStripControls.find('[timeline-action="playpause"]')
var speedButtons = timeStripControls.find('[timeline-action="speed"]') var speedButtons = timeStripControls.find('[timeline-action="speed"]')
var gridSizeButtons = timeStripControls.find('[timeline-action="gridSize"]') var gridSizeButtons = timeStripControls.find('[timeline-action="gridSize"]')
@ -50,6 +51,8 @@ $(document).ready(function(){
var dateRangeChanging = false var dateRangeChanging = false
var lastDateChecked = new Date(0) var lastDateChecked = new Date(0)
var monitorSelectionElements = [] var monitorSelectionElements = []
var sideMenuListMissing = sideMenuList.length === 0;
var selectingMonitorList = (sideMenuListMissing ? monitorList : sideMenuList);
function setLoadingMask(turnOn){ function setLoadingMask(turnOn){
if(turnOn){ if(turnOn){
if(theWindow.find('.loading-mask').length === 0){ if(theWindow.find('.loading-mask').length === 0){
@ -441,7 +444,7 @@ $(document).ready(function(){
function setVideoInCanvas(newVideo){ function setVideoInCanvas(newVideo){
var monitorId = newVideo.mid var monitorId = newVideo.mid
var container = getVideoContainerInCanvas(newVideo) 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 vidEl = getVideoElInCanvas(newVideo)
var objectContainer = getObjectContainerInCanvas(newVideo) var objectContainer = getObjectContainerInCanvas(newVideo)
vidEl.playbackRate = timelineSpeed vidEl.playbackRate = timelineSpeed
@ -821,15 +824,16 @@ $(document).ready(function(){
}) })
}) })
var html = buildSubMenuItems(allFound) var html = buildSubMenuItems(allFound)
sideMenuList.html(html) if(!sideMenuListMissing)sideMenuList.html(html)
monitorSelectionElements = sideMenuList.find('.timeline-selectMonitor') monitorList.html(html)
monitorSelectionElements = selectingMonitorList.find('.timeline-selectMonitor')
} }
async function setSideMenuMonitorVisualSelection(){ async function setSideMenuMonitorVisualSelection(){
var getForAllMonitors = timeStripSelectedMonitors.length === 0; var getForAllMonitors = timeStripSelectedMonitors.length === 0;
monitorSelectionElements.find('.dot').removeClass('dot-green') monitorSelectionElements.find('.dot').removeClass('dot-green')
if(!getForAllMonitors){ if(!getForAllMonitors){
timeStripSelectedMonitors.forEach((monitorId) => { 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() refreshTimeline()
} }
} }
sideMenuList.on('click','[timeline-menu-action]',function(){ function monitorSelectorController(){
var el = $(this) var el = $(this)
var type = el.attr('timeline-menu-action') var type = el.attr('timeline-menu-action')
switch(type){ switch(type){
@ -957,7 +961,9 @@ $(document).ready(function(){
} }
break; break;
} }
}) }
monitorList.on('click','[timeline-menu-action]', monitorSelectorController)
sideMenuList.on('click','[timeline-menu-action]', monitorSelectorController)
timelineActionButtons.click(function(){ timelineActionButtons.click(function(){
var el = $(this) var el = $(this)
var type = el.attr('timeline-action') var type = el.attr('timeline-action')

View File

@ -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;
})
})

View File

@ -658,158 +658,3 @@ function getDisplayDimensions(videoElement) {
videoHeight: displayHeight, 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;
})
})

View File

@ -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()
})
})

View File

@ -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()
})
})

View File

@ -7,7 +7,6 @@ $(document).ready(function(){
var remoteDashboardLinkButton = easyRemoteAccessTab.find('.remote-dashboard-link') var remoteDashboardLinkButton = easyRemoteAccessTab.find('.remote-dashboard-link')
var loadingRegistration = false var loadingRegistration = false
var statusConnections = {} var statusConnections = {}
var currentlyRegisteredP2PServer = currentlySelectedP2PServerId ? currentlySelectedP2PServerId + '' : undefined
function copyToClipboard(str) { function copyToClipboard(str) {
const el = document.createElement('textarea'); const el = document.createElement('textarea');
el.value = str; el.value = str;
@ -72,37 +71,11 @@ $(document).ready(function(){
loadingRegistration = false loadingRegistration = false
easyRemoteAccessTab.find('.remote-dashboard-link').html(`<i class="fa fa-external-link"></i> ` + lang['Open Remote Dashboard']) 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']) 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){ function makeHostLink(selectedServer,apiKey){
var href = `https://${selectedServer.host}:${selectedServer.webPort == 80 ? 443 : selectedServer.webPort}/s/${apiKey}/` var href = `https://${selectedServer.host}:${selectedServer.webPort == 80 ? 443 : selectedServer.webPort}/s/${apiKey}/`
return href 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(){ function beginAllStatusConnections(){
$.each(p2pServerList,function(key,server){ $.each(p2pServerList,function(key,server){
server.key = key server.key = key
@ -127,6 +100,14 @@ $(document).ready(function(){
toggleAffected.hide() 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) p2pEnabledSwitch.change(setVisibilityForList)
easyRemoteAccessTab.find('.submit').click(function(){ easyRemoteAccessTab.find('.submit').click(function(){
easyRemoteAccessForm.submit() easyRemoteAccessForm.submit()
@ -135,39 +116,51 @@ $(document).ready(function(){
e.preventDefault() e.preventDefault()
var formValues = $(this).serializeObject() var formValues = $(this).serializeObject()
disableForm() disableForm()
formValues.p2pHostSelected = currentlySelectedP2PServerId // formValues.p2pHostSelected = currentlySelectedP2PServerId
formValues.p2pHostMultiSelected = getSelectedServers()
console.log(formValues) console.log(formValues)
$.post(superApiPrefix + $user.sessionKey + '/p2p/save',{ $.post(superApiPrefix + $user.sessionKey + '/p2p/save',{
data: JSON.stringify(formValues) data: JSON.stringify(formValues)
},function(data){ },function(data){
console.log(data) console.log(data)
if(data.ok){ if(data.ok){
currentlyRegisteredP2PServer = currentlySelectedP2PServerId + ''
new PNotify({ new PNotify({
type: 'success', type: 'success',
title: lang['P2P Settings Applied'], title: lang['P2P Settings Applied'],
text: lang.p2pSettingsText1, text: lang.p2pSettingsText1,
}) })
setCurrentRemoteLink()
setTimeout(enableForm,5000) setTimeout(enableForm,5000)
} }
}) })
return false return false
}) })
easyRemoteAccessForm.on('click','[drawn-id]',function(){ easyRemoteAccessTab.on('click','.activate-remote-selection',function(e){
e.preventDefault()
var el = $(this) var el = $(this)
var p2pServerId = el.attr('drawn-id') var parent = el.parents('[drawn-id]')
easyRemoteAccessForm.find('[drawn-id]').removeClass('active') var drawnId = parent.attr('drawn-id')
el.addClass('active') var alreadyActive = parent.hasClass('active')
currentlySelectedP2PServerId = p2pServerId 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){ easyRemoteAccessTab.on('click','.remote-dashboard-link-copy',function(e){
e.preventDefault() e.preventDefault()
if(!loadingRegistration){ if(!loadingRegistration){
var parent = $(this).parents('[drawn-id]')
var drawnId = parent.attr('drawn-id')
var apiKey = easyRemoteAccessForm.find('[name="p2pApiKey"]').val() var apiKey = easyRemoteAccessForm.find('[name="p2pApiKey"]').val()
var selectedServer = p2pServerList[currentlyRegisteredP2PServer] var selectedServer = p2pServerList[drawnId]
console.log(selectedServer,currentlySelectedP2PServerId,p2pServerList) if(parent.hasClass('active') && selectedServer && selectedServer.host){
if(selectedServer && selectedServer.host){
var href = makeHostLink(selectedServer,apiKey) var href = makeHostLink(selectedServer,apiKey)
copyToClipboard(href) copyToClipboard(href)
new PNotify({ 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; return false;
}) })
setVisibilityForList() setVisibilityForList()
displayCurrentlySelectedInternally()
}) })

View File

@ -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> <style>
.epic-text-filter { .epic-text-filter {
@ -19,8 +21,9 @@
</style> </style>
<script> <script>
window.p2pServerList = <%- JSON.stringify(p2pServerList) %> window.p2pServerList = <%- JSON.stringify(p2pServerList) %>
window.multipleSelected = <%- multipleSelected %>
window.selectedServers = <%- JSON.stringify(selectedServers) %>
window.useBetterP2P = <%- !!config.useBetterP2P %> window.useBetterP2P = <%- !!config.useBetterP2P %>
window.currentlySelectedP2PServerId = `<%- config.p2pHostSelected %>`
</script> </script>
<link rel="stylesheet" href="<%-window.libURL%>assets/css/super.easyRemoteAccess.css"> <link rel="stylesheet" href="<%-window.libURL%>assets/css/super.easyRemoteAccess.css">
<form> <form>
@ -40,12 +43,6 @@
<div class="form-group p2p-toggle-affected"> <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 %>"> <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>
<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"> <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> <a href="#" class="submit flex-grow-1 btn btn-success"><i class="fa fa-check"></i> <%- lang.Save %></a>
</div> </div>
@ -72,7 +69,7 @@
if(!config.useBetterP2P && details.v2)return; if(!config.useBetterP2P && details.v2)return;
%> %>
<div class="col-md-4"> <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"> <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-danger pull-right selected-badge"><%- lang.Selected %></span>
<span class="badge badge-sm badge-info name-badge"><%- details.name.toUpperCase() %></span> <span class="badge badge-sm badge-info name-badge"><%- details.name.toUpperCase() %></span>
@ -107,7 +104,11 @@
</div> </div>
</div> </div>
<div class="card-footer" style="min-height:auto"> <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> </div>
</div> </div>
@ -115,7 +116,6 @@
</div> </div>
<div class="form-group p2p-toggle-affected"> <div class="form-group p2p-toggle-affected">
<div class="btn-group d-flex flex-row"> <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> <a href="#" class="submit flex-grow-1 btn btn-success"><i class="fa fa-check"></i> <%- lang.Save %></a>
</div> </div>
</div> </div>

View File

@ -21,12 +21,15 @@
<script src="<%-window.libURL%>assets/vendor/js/clock.js"></script> <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/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.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.extenders.js"></script>
<script src="<%-window.libURL%>assets/js/bs5.websocket.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.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.videoPlayer.js"></script>
<script src="<%-window.libURL%>assets/js/bs5.livePlayer.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.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.subAccountManager.js"></script>
<script src="<%-window.libURL%>assets/js/bs5.apiKeys.js"></script> <script src="<%-window.libURL%>assets/js/bs5.apiKeys.js"></script>
<script src="<%-window.libURL%>assets/js/bs5.monitorSettings.js"></script> <script src="<%-window.libURL%>assets/js/bs5.monitorSettings.js"></script>

View File

@ -42,12 +42,16 @@
</main> </main>
<script> <script>
var monitorSettingsAdditionalInputMapFieldHtml = `<% Object.keys(define["Monitor Settings Additional Input Map"].blocks).forEach(function(blockKey){ var monitorSettingsAdditionalInputMapFieldHtml = `<% Object.keys(define["Monitor Settings Additional Input Map"].blocks).forEach(function(blockKey){ %>
drawBlock(define["Monitor Settings Additional Input Map"].blocks[blockKey]) <%- include('drawBlock', {
}) %>` theBlock: define["Monitor Settings Additional Input Map"].blocks[blockKey]
}) %>
<% }) %>`
</script> </script>
<script> <script>
var monitorSettingsAdditionalStreamChannelFieldHtml = `<% Object.keys(define["Monitor Settings Additional Stream Channel"].blocks).forEach(function(blockKey){ var monitorSettingsAdditionalStreamChannelFieldHtml = `<% Object.keys(define["Monitor Settings Additional Stream Channel"].blocks).forEach(function(blockKey){ %>
drawBlock(define["Monitor Settings Additional Stream Channel"].blocks[blockKey]) <%- include('drawBlock', {
}) %>` theBlock: define["Monitor Settings Additional Stream Channel"].blocks[blockKey]
}) %>
<% }) %>`
</script> </script>

View File

@ -17,10 +17,7 @@
var buildOptions var buildOptions
%> %>
<form class="dark"> <form class="dark">
<%- include('home/fieldBuilders'); %> <%- include('home/drawBlock', { theBlock: define['LDAP'].blocks.LDAP }) %>
<%
drawBlock(define['LDAP'].blocks.LDAP)
%>
</form> </form>
<script> <script>
$(document).ready(function() { $(document).ready(function() {

View File

@ -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>

78
web/pages/wallview.ejs Normal file
View File

@ -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>