Use Old ONVIF Scanner
parent
384752a422
commit
0c5485409d
|
@ -6625,75 +6625,79 @@ module.exports = function(s,config,lang){
|
|||
},
|
||||
}
|
||||
},
|
||||
"ONVIF Scanner": {
|
||||
"section": "ONVIF Scanner",
|
||||
"blocks": {
|
||||
"Search Settings": {
|
||||
"name": lang["Scan Settings"],
|
||||
"color": "navy",
|
||||
"blockquote": lang.ONVIFnote,
|
||||
"section-pre-class": "col-md-4",
|
||||
"info": [
|
||||
{
|
||||
"name": "ip",
|
||||
"field": lang['IP Address'],
|
||||
"description": lang.fieldTextIp,
|
||||
"example": "10.1.100.1-10.1.100.254",
|
||||
},
|
||||
{
|
||||
"name": "port",
|
||||
"field": lang['Port'],
|
||||
"description": lang.separateByCommasOrRange,
|
||||
"example": "80,7575,8000,8080,8081",
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"field": lang['Camera Username'],
|
||||
"description": lang.fieldTextOnvifScanCameraUsername,
|
||||
"placeholder": "Can be left blank.",
|
||||
},
|
||||
{
|
||||
"name": "pass",
|
||||
"field": lang['Camera Password'],
|
||||
"description": lang.fieldTextOnvifScanCameraPassword,
|
||||
"fieldType": "password",
|
||||
},
|
||||
{
|
||||
"fieldType": "btn-group",
|
||||
"btns": [
|
||||
{
|
||||
"fieldType": "btn",
|
||||
"forForm": true,
|
||||
"class": `btn-success start-scan`,
|
||||
"btnContent": `${lang['Search']}`,
|
||||
},
|
||||
{
|
||||
"fieldType": "btn",
|
||||
"class": `btn-danger stop-scan d-none`,
|
||||
"btnContent": `${lang['Stop']}`,
|
||||
},
|
||||
{
|
||||
"fieldType": "btn",
|
||||
"class": `btn-default add-all`,
|
||||
"btnContent": `${lang['Add All']}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
"ONVIF Scanner": {
|
||||
"section": "ONVIF Scanner",
|
||||
"blocks": {
|
||||
"Search Settings": {
|
||||
"name": lang["Scan Settings"],
|
||||
"color": "navy",
|
||||
"blockquote": lang.ONVIFnote,
|
||||
"section-pre-class": "col-md-4",
|
||||
"info": [
|
||||
{
|
||||
"name": "ip",
|
||||
"field": lang['IP Address'],
|
||||
"description": lang[lang["fieldTextIp"]],
|
||||
"example": "10.1.100.1-10.1.100.254",
|
||||
},
|
||||
{
|
||||
"name": "port",
|
||||
"field": lang['Port'],
|
||||
"description": lang.separateByCommasOrRange,
|
||||
"example": "80,7575,8000,8080,8081",
|
||||
},
|
||||
{
|
||||
"name": "user",
|
||||
"field": lang['Camera Username'],
|
||||
"placeholder": "Can be left blank.",
|
||||
},
|
||||
{
|
||||
"name": "pass",
|
||||
"field": lang['Camera Password'],
|
||||
"fieldType": "password",
|
||||
},
|
||||
{
|
||||
"fieldType": "btn-group",
|
||||
"btns": [
|
||||
{
|
||||
"fieldType": "btn",
|
||||
"forForm": true,
|
||||
"class": `btn-block btn-success`,
|
||||
"btnContent": `${lang['Search']}<span class="_loading" style="display:none"> <i class="fa fa-pulse fa-spinner"></i></span>`,
|
||||
},
|
||||
{
|
||||
"fieldType": "btn",
|
||||
"class": `btn-default add-all`,
|
||||
"btnContent": `${lang['Add All']}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
"Found Devices": {
|
||||
"name": lang['Found Devices'],
|
||||
"color": "blue",
|
||||
"section-pre-class": "col-md-8",
|
||||
"info": [
|
||||
{
|
||||
"fieldType": "div",
|
||||
"class": "onvif_result row",
|
||||
}
|
||||
]
|
||||
},
|
||||
"Found Devices": {
|
||||
"name": lang['Found Devices'],
|
||||
"color": "blue",
|
||||
"section-pre-class": "col-md-8",
|
||||
"Other Devices": {
|
||||
"name": lang['Other Devices'],
|
||||
"color": "danger",
|
||||
"section-pre-class": "col-md-12",
|
||||
"info": [
|
||||
{
|
||||
"fieldType": "div",
|
||||
"class": "onvif_result row",
|
||||
"class": "onvif_result_error row",
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
"Camera Probe": {
|
||||
"section": "Camera Probe",
|
||||
"blocks": {
|
||||
|
|
|
@ -1,34 +1,16 @@
|
|||
module.exports = function(s,config,lang,app,io){
|
||||
const {
|
||||
scanStatus,
|
||||
runOnvifScanner,
|
||||
stopOnvifScanner,
|
||||
} = require('./scanners/utils.js')(s,config,lang)
|
||||
const {
|
||||
ffprobe,
|
||||
} = require('./ffmpeg/utils.js')(s,config,lang)
|
||||
const {
|
||||
runOnvifScanner,
|
||||
} = require('./scanners/utils.js')(s,config,lang)
|
||||
const onWebSocketConnection = async (cn) => {
|
||||
const tx = function(z){if(!z.ke){z.ke=cn.ke;};cn.emit('f',z);}
|
||||
cn.on('f',(d) => {
|
||||
switch(d.f){
|
||||
case'onvif_scan_reconnect':
|
||||
tx({f: 'onvif_scan_current', devices: Object.values(scanStatus.allSuccessful), isScanning: scanStatus.isActive})
|
||||
break;
|
||||
case'onvif_stop':
|
||||
stopOnvifScanner()
|
||||
tx({f: 'onvif_scan_stopped'})
|
||||
break;
|
||||
case'onvif':
|
||||
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)
|
||||
})
|
||||
runOnvifScanner(d,tx)
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
@ -58,60 +40,4 @@ module.exports = function(s,config,lang,app,io){
|
|||
})
|
||||
},res,req);
|
||||
})
|
||||
/**
|
||||
* API : ONVIF Scanner RUN
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/onvifScanner/:ke/scan',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Authorized']
|
||||
});
|
||||
return
|
||||
}
|
||||
const groupKey = req.params.ke;
|
||||
stopOnvifScanner()
|
||||
s.closeJsonResponse(res, { ok: true });
|
||||
},res,req);
|
||||
})
|
||||
/**
|
||||
* API : ONVIF Scanner STOP
|
||||
*/
|
||||
app.get(config.webPaths.apiPrefix+':auth/onvifScanner/:ke/scan/stop',function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
const {
|
||||
isRestricted,
|
||||
isRestrictedApiKey,
|
||||
apiKeyPermissions,
|
||||
} = s.checkPermission(user);
|
||||
if(
|
||||
isRestrictedApiKey && apiKeyPermissions.control_monitors_disallowed
|
||||
){
|
||||
s.closeJsonResponse(res,{
|
||||
ok: false,
|
||||
msg: lang['Not Authorized']
|
||||
});
|
||||
return
|
||||
}
|
||||
const groupKey = req.params.ke;
|
||||
runOnvifScanner(d, (data) => {
|
||||
const response = { f: 'onvif', ...data }
|
||||
s.tx(response, 'GRP_' + groupKey)
|
||||
}, (data) => {
|
||||
const response = { f: 'onvif', ff: 'failed_capture', ...data }
|
||||
s.tx(response, 'GRP_' + groupKey)
|
||||
}).then((responseList) => {
|
||||
s.tx({ f: 'onvif_scan_complete' }, 'GRP_' + groupKey)
|
||||
s.closeJsonResponse(res, responseList)
|
||||
})
|
||||
},res,req);
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,381 +1,202 @@
|
|||
var os = require('os');
|
||||
const async = require('async');
|
||||
const onvif = require("shinobi-onvif");
|
||||
const {
|
||||
addCredentialsToUrl,
|
||||
stringContains,
|
||||
getBuffer,
|
||||
} = require('../common.js')
|
||||
const scanStatus = {
|
||||
current: [],
|
||||
allSuccessful: {},
|
||||
cancelPromises: null,
|
||||
abortController: null
|
||||
};
|
||||
|
||||
module.exports = (s,config,lang) => {
|
||||
const ipRange = (start_ip, end_ip) => {
|
||||
const startLong = toLong(start_ip);
|
||||
const endLong = toLong(end_ip);
|
||||
if (startLong > endLong) {
|
||||
const tmp = startLong;
|
||||
startLong = endLong;
|
||||
endLong = tmp;
|
||||
}
|
||||
const rangeArray = [];
|
||||
for (let i = startLong; i <= endLong; i++) {
|
||||
rangeArray.push(fromLong(i));
|
||||
}
|
||||
return rangeArray;
|
||||
};
|
||||
|
||||
const portRange = (lowEnd, highEnd) => {
|
||||
const list = [];
|
||||
for (let i = lowEnd; i <= highEnd; i++) {
|
||||
var startLong = toLong(start_ip);
|
||||
var endLong = toLong(end_ip);
|
||||
if (startLong > endLong) {
|
||||
var tmp = startLong;
|
||||
startLong = endLong
|
||||
endLong = tmp;
|
||||
}
|
||||
var rangeArray = [];
|
||||
var i;
|
||||
for (i = startLong; i <= endLong; i++) {
|
||||
rangeArray.push(fromLong(i));
|
||||
}
|
||||
return rangeArray;
|
||||
}
|
||||
const portRange = (lowEnd,highEnd) => {
|
||||
var list = [];
|
||||
for (var i = lowEnd; i <= highEnd; i++) {
|
||||
list.push(i);
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
}
|
||||
//toLong taken from NPM package 'ip'
|
||||
const toLong = (ip) => {
|
||||
let ipl = 0;
|
||||
ip.split('.').forEach(function(octet) {
|
||||
ipl <<= 8;
|
||||
ipl += parseInt(octet);
|
||||
});
|
||||
return (ipl >>> 0);
|
||||
};
|
||||
|
||||
var ipl = 0;
|
||||
ip.split('.').forEach(function(octet) {
|
||||
ipl <<= 8;
|
||||
ipl += parseInt(octet);
|
||||
});
|
||||
return(ipl >>> 0);
|
||||
}
|
||||
//fromLong taken from NPM package 'ip'
|
||||
const fromLong = (ipl) => {
|
||||
return ((ipl >>> 24) + '.' +
|
||||
(ipl >> 16 & 255) + '.' +
|
||||
(ipl >> 8 & 255) + '.' +
|
||||
(ipl & 255));
|
||||
};
|
||||
|
||||
const getNetworkAddresses = () => {
|
||||
const interfaces = os.networkInterfaces();
|
||||
const addresses = [];
|
||||
for (const k in interfaces) {
|
||||
for (const k2 in interfaces[k]) {
|
||||
const address = interfaces[k][k2];
|
||||
if (address.family === 'IPv4' && !address.internal) {
|
||||
addresses.push(address.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
return addresses;
|
||||
};
|
||||
|
||||
const getAddressRange = (addresses) => {
|
||||
const addressRange = [];
|
||||
addresses.forEach((address) => {
|
||||
if (address.indexOf('0.0.0') > -1) return;
|
||||
const addressPrefix = address.split('.').slice(0, 3).join('.');
|
||||
addressRange.push(`${addressPrefix}.1-${addressPrefix}.254`);
|
||||
});
|
||||
return addressRange.join(',');
|
||||
};
|
||||
|
||||
const getPorts = (ports) => {
|
||||
if (ports === '') {
|
||||
return '80,8080,8000,7575,8081,9080,8090,8999,8899'.split(',');
|
||||
}
|
||||
if (ports.indexOf('-') > -1) {
|
||||
const [start, end] = ports.split('-');
|
||||
return portRange(start, end);
|
||||
}
|
||||
return ports.split(',');
|
||||
};
|
||||
|
||||
const getIpList = (ip) => {
|
||||
const ipList = [];
|
||||
ip.split(',').forEach((range) => {
|
||||
const [start, end] = range.indexOf('-') > -1 ? range.split('-') : [range, range];
|
||||
ipList.push(...ipRange(start, end));
|
||||
});
|
||||
return ipList;
|
||||
};
|
||||
|
||||
const createHitList = (ipList, ports, onvifUsername = '', onvifPassword = '') => {
|
||||
const hitList = [];
|
||||
const usernameVariants = onvifUsername.split(',');
|
||||
const passwordVariants = onvifPassword.split(',');
|
||||
for (const username of usernameVariants) {
|
||||
for (const password of passwordVariants) {
|
||||
hitList.push(...ipList.flatMap((ipEntry) =>
|
||||
ports.map((portEntry) => ({
|
||||
xaddr: `http://${ipEntry}:${portEntry}/onvif/device_service`,
|
||||
user: username,
|
||||
pass: password,
|
||||
ip: ipEntry,
|
||||
port: portEntry,
|
||||
}))
|
||||
));
|
||||
}
|
||||
}
|
||||
return hitList;
|
||||
};
|
||||
|
||||
const takeSnapshot = async (cameraResponse, device) => {
|
||||
try {
|
||||
const snapUri = addCredentialsToUrl({
|
||||
username: cameraResponse.user,
|
||||
password: cameraResponse.pass,
|
||||
url: (await device.services.media.getSnapshotUri({ ProfileToken: device.current_profile.token })).data.GetSnapshotUriResponse.MediaUri.Uri,
|
||||
});
|
||||
const imgBuffer = await getBuffer(snapUri);
|
||||
cameraResponse.snapShot = imgBuffer.toString('base64');
|
||||
} catch (err) {
|
||||
console.error('Failed to get snapshot via ONVIF:', err);
|
||||
}
|
||||
return cameraResponse;
|
||||
return ((ipl >>> 24) + '.' +
|
||||
(ipl >> 16 & 255) + '.' +
|
||||
(ipl >> 8 & 255) + '.' +
|
||||
(ipl & 255) );
|
||||
}
|
||||
|
||||
const fetchCameraDetails = async (camera, onvifUsername, onvifPassword, foundCameraCallback, failedCameraCallback) => {
|
||||
// const previousSuccess = scanStatus.allSuccessful[camera.ip];
|
||||
// if (previousSuccess) {
|
||||
// // console.log('FOUND PREVIOUS', camera.ip);
|
||||
// foundCameraCallback(previousSuccess);
|
||||
// return;
|
||||
// }
|
||||
try {
|
||||
const device = new onvif.OnvifDevice(camera);
|
||||
const info = await device.init();
|
||||
const stream = await device.services.media.getStreamUri({
|
||||
ProfileToken: device.current_profile.token,
|
||||
Protocol: 'RTSP'
|
||||
});
|
||||
|
||||
const cameraResponse = {
|
||||
ip: camera.ip,
|
||||
port: camera.port,
|
||||
user: camera.user,
|
||||
pass: camera.pass,
|
||||
info: info,
|
||||
uri: stream.data.GetStreamUriResponse.MediaUri.Uri
|
||||
};
|
||||
|
||||
try {
|
||||
const camPtzConfigs = (await device.services.ptz.getConfigurations()).data.GetConfigurationsResponse;
|
||||
if (camPtzConfigs.PTZConfiguration && (camPtzConfigs.PTZConfiguration.PanTiltLimits || camPtzConfigs.PTZConfiguration.ZoomLimits)) {
|
||||
cameraResponse.isPTZ = true;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(er)
|
||||
// s.debugLog(err);
|
||||
}
|
||||
await takeSnapshot(cameraResponse, device)
|
||||
scanStatus.allSuccessful[camera.ip] = cameraResponse;
|
||||
foundCameraCallback(cameraResponse);
|
||||
|
||||
return cameraResponse;
|
||||
} catch (err) {
|
||||
return handleCameraError(camera, err, failedCameraCallback);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCameraError = (camera, err, failedCameraCallback) => {
|
||||
// const previousSuccess = scanStatus.allSuccessful[camera.ip];
|
||||
// if (previousSuccess) {
|
||||
// // console.log('FOUND PREVIOUS AFTER ERROR', camera.ip);
|
||||
// return previousSuccess;
|
||||
// }
|
||||
const searchError = (find) => stringContains(find, err.message, true);
|
||||
const commonIgnoredErrors = ['ECONNREFUSED', 'socket hang up'];
|
||||
let foundDevice = false;
|
||||
let errorMessage = '';
|
||||
|
||||
switch (true) {
|
||||
case searchError('ECONNREFUSED'):
|
||||
errorMessage = `ECONNREFUSED`;
|
||||
return {refused: true}
|
||||
break;
|
||||
case searchError('TIMEDOUT'):
|
||||
foundDevice = true;
|
||||
errorMessage = lang.ONVIFErr401;
|
||||
break;
|
||||
case searchError('401'):
|
||||
foundDevice = true;
|
||||
errorMessage = lang.ONVIFErr401;
|
||||
break;
|
||||
case searchError('400'):
|
||||
foundDevice = true;
|
||||
errorMessage = lang.ONVIFErr400;
|
||||
break;
|
||||
case searchError('405'):
|
||||
foundDevice = true;
|
||||
errorMessage = lang.ONVIFErr405;
|
||||
break;
|
||||
case searchError('404'):
|
||||
foundDevice = true;
|
||||
errorMessage = lang.ONVIFErr404;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (foundDevice) {
|
||||
const cameraResponse = {
|
||||
ip: camera.ip,
|
||||
port: camera.port,
|
||||
error: errorMessage,
|
||||
failedConnection: true,
|
||||
};
|
||||
failedCameraCallback(cameraResponse);
|
||||
return cameraResponse;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
function isValidOnvifResult(result) {
|
||||
return result.info || result.uri;
|
||||
}
|
||||
|
||||
function detectAndReplaceReolinkRTSP(camera, url){
|
||||
const possibilities = [`/h264Preview_01_main`, `/h265Preview_01_main`]
|
||||
for(possible of possibilities){
|
||||
// console.log(url, possible, url.indexOf(possible) > -1)
|
||||
if(url.indexOf(possible) > -1){
|
||||
return `rtmp://${camera.user}:${camera.pass}@${camera.ip}:1935/bcs/channel0_main.bcs?token=sdasdasd&channel=0&stream=0&user=${camera.user}&password=${camera.pass}`
|
||||
}
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
const runOnvifScanner = async (options, foundCameraCallback, failedCameraCallback) => {
|
||||
if (scanStatus.isActive) return scanStatus.current;
|
||||
|
||||
scanStatus.isActive = true;
|
||||
scanStatus.abortController = new AbortController();
|
||||
const { signal } = scanStatus.abortController;
|
||||
const cancelPromises = [];
|
||||
scanStatus.cancelPromises = cancelPromises;
|
||||
let ip = options.ip.replace(/ /g, '');
|
||||
let ports = options.port.replace(/ /g, '');
|
||||
const onvifUsername = options.user || 'admin';
|
||||
const onvifPassword = options.pass || '';
|
||||
|
||||
if (ip === '') {
|
||||
const addresses = getNetworkAddresses();
|
||||
ip = getAddressRange(addresses);
|
||||
}
|
||||
|
||||
ports = getPorts(ports);
|
||||
|
||||
const ipList = getIpList(ip);
|
||||
const hitList = createHitList(ipList, ports, onvifUsername, onvifPassword);
|
||||
|
||||
const ipQueues = {};
|
||||
const responseList = [];
|
||||
const allPingSuccess = {};
|
||||
|
||||
const fetchWithTimeout = async (camera, onvifUsername, onvifPassword, foundCameraCallback, failedCameraCallback, signal) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error('Timeout')), 2500); // Adjust the timeout as needed
|
||||
fetchCameraDetails(camera, onvifUsername, onvifPassword, foundCameraCallback, failedCameraCallback, signal)
|
||||
.then(result => {
|
||||
clearTimeout(timeout);
|
||||
resolve(result);
|
||||
})
|
||||
.catch(error => {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
for (const camera of hitList) {
|
||||
if (!ipQueues[camera.ip]) {
|
||||
ipQueues[camera.ip] = async.queue(async (task) => {
|
||||
if (signal.aborted) {
|
||||
throw new Error('Aborted');
|
||||
}
|
||||
// if(!scanStatus.allSuccessful[camera.ip]){
|
||||
const cameraIp = task.camera.ip;
|
||||
const hasPingSuccess = allPingSuccess[cameraIp];
|
||||
if (hasPingSuccess !== false) {
|
||||
const fetchPromise = fetchWithTimeout(task.camera, task.onvifUsername, task.onvifPassword, task.foundCameraCallback, task.failedCameraCallback, signal);
|
||||
cancelPromises.push(fetchPromise);
|
||||
const result = await fetchPromise;
|
||||
if (result.refused) allPingSuccess[cameraIp] = !result.refused;
|
||||
if (result.uri){
|
||||
try{
|
||||
result.uri = detectAndReplaceReolinkRTSP(task.camera, addCredentialsToUrl({ url: result.uri, username: task.camera.user, password: task.camera.pass }));
|
||||
}catch(err){
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
responseList.push({...result});
|
||||
}
|
||||
// }
|
||||
}, 1);
|
||||
}
|
||||
ipQueues[camera.ip].push({
|
||||
camera,
|
||||
onvifUsername: camera.user,
|
||||
onvifPassword: camera.pass,
|
||||
foundCameraCallback,
|
||||
failedCameraCallback
|
||||
});
|
||||
}
|
||||
await Promise.all(Object.values(ipQueues).map(queue => new Promise((resolve) => queue.drain(resolve))));
|
||||
} catch (err) {
|
||||
if (err.message === 'Aborted') {
|
||||
console.log('Scan aborted');
|
||||
} else {
|
||||
console.error('big error', err);
|
||||
}
|
||||
}
|
||||
|
||||
scanStatus.isActive = false;
|
||||
scanStatus.abortController = null;
|
||||
scanStatus.cancelPromises = null;
|
||||
s.debugLog('Done Scan');
|
||||
return responseList;
|
||||
};
|
||||
|
||||
const stopOnvifScanner = () => {
|
||||
if (scanStatus.isActive && scanStatus.abortController) {
|
||||
scanStatus.abortController.abort();
|
||||
scanStatus.cancelPromises.forEach(promise => promise.catch(() => {}));
|
||||
scanStatus.isActive = false;
|
||||
s.debugLog('Scan stopped');
|
||||
}
|
||||
};
|
||||
|
||||
function expandIPRange(rangeStr) {
|
||||
const ipRangeToArray = (start, end) => {
|
||||
const startParts = start.split('.').map(Number);
|
||||
const endParts = end.split('.').map(Number);
|
||||
const ips = [];
|
||||
for (let a = startParts[0]; a <= endParts[0]; a++) {
|
||||
for (let b = startParts[1]; b <= endParts[1]; b++) {
|
||||
for (let c = startParts[2]; c <= endParts[2]; c++) {
|
||||
for (let d = startParts[3]; d <= endParts[3]; d++) {
|
||||
ips.push([a, b, c, d].join('.'));
|
||||
}
|
||||
const runOnvifScanner = (options,foundCameraCallback) => {
|
||||
var ip = options.ip.replace(/ /g,'')
|
||||
var ports = options.port.replace(/ /g,'')
|
||||
if(options.ip === ''){
|
||||
var interfaces = os.networkInterfaces()
|
||||
var addresses = []
|
||||
for (var k in interfaces) {
|
||||
for (var k2 in interfaces[k]) {
|
||||
var address = interfaces[k][k2]
|
||||
if (address.family === 'IPv4' && !address.internal) {
|
||||
addresses.push(address.address)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ips;
|
||||
};
|
||||
const addressRange = []
|
||||
addresses.forEach(function(address){
|
||||
if(address.indexOf('0.0.0')>-1){return false}
|
||||
var addressPrefix = address.split('.')
|
||||
delete(addressPrefix[3]);
|
||||
addressPrefix = addressPrefix.join('.')
|
||||
addressRange.push(`${addressPrefix}1-${addressPrefix}254`)
|
||||
})
|
||||
ip = addressRange.join(',')
|
||||
}
|
||||
if(ports === ''){
|
||||
ports = '80,8080,8000,7575,8081,9080,8090,8999,8899'
|
||||
}
|
||||
if(ports.indexOf('-') > -1){
|
||||
ports = ports.split('-')
|
||||
var portRangeStart = ports[0]
|
||||
var portRangeEnd = ports[1]
|
||||
ports = portRange(portRangeStart,portRangeEnd);
|
||||
}else{
|
||||
ports = ports.split(',')
|
||||
}
|
||||
var ipList = options.ipList
|
||||
var onvifUsername = options.user || ''
|
||||
var onvifPassword = options.pass || ''
|
||||
ip.split(',').forEach(function(addressRange){
|
||||
var ipRangeStart = addressRange[0]
|
||||
var ipRangeEnd = addressRange[1]
|
||||
if(addressRange.indexOf('-')>-1){
|
||||
addressRange = addressRange.split('-');
|
||||
ipRangeStart = addressRange[0]
|
||||
ipRangeEnd = addressRange[1]
|
||||
}else{
|
||||
ipRangeStart = addressRange
|
||||
ipRangeEnd = addressRange
|
||||
}
|
||||
if(!ipList){
|
||||
ipList = ipRange(ipRangeStart,ipRangeEnd);
|
||||
}else{
|
||||
ipList = ipList.concat(ipRange(ipRangeStart,ipRangeEnd))
|
||||
}
|
||||
})
|
||||
var hitList = []
|
||||
ipList.forEach((ipEntry,n) => {
|
||||
ports.forEach((portEntry,nn) => {
|
||||
hitList.push({
|
||||
xaddr : 'http://' + ipEntry + ':' + portEntry + '/onvif/device_service',
|
||||
user : onvifUsername,
|
||||
pass : onvifPassword,
|
||||
ip: ipEntry,
|
||||
port: portEntry,
|
||||
})
|
||||
})
|
||||
})
|
||||
var responseList = []
|
||||
hitList.forEach(async (camera) => {
|
||||
try{
|
||||
var device = new onvif.OnvifDevice(camera)
|
||||
var info = await device.init()
|
||||
var date = await device.services.device.getSystemDateAndTime()
|
||||
var stream = await device.services.media.getStreamUri({
|
||||
ProfileToken : device.current_profile.token,
|
||||
Protocol : 'RTSP'
|
||||
})
|
||||
|
||||
return rangeStr.split(',')
|
||||
.flatMap(range => {
|
||||
const [start, end] = range.split('-');
|
||||
return ipRangeToArray(start.trim(), end.trim());
|
||||
});
|
||||
var cameraResponse = {
|
||||
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{
|
||||
const snapUri = addCredentialsToUrl({
|
||||
username: onvifUsername,
|
||||
password: onvifPassword,
|
||||
url: (await device.services.media.getSnapshotUri({
|
||||
ProfileToken : device.current_profile.token,
|
||||
})).data.GetSnapshotUriResponse.MediaUri.Uri,
|
||||
});
|
||||
imageSnap = (await getBuffer(snapUri)).toString('base64');
|
||||
}catch(err){
|
||||
s.debugLog(err)
|
||||
}
|
||||
if(foundCameraCallback)foundCameraCallback(Object.assign(cameraResponse,{f: 'onvif', snapShot: imageSnap}))
|
||||
}catch(err){
|
||||
const searchError = (find) => {
|
||||
return stringContains(find,err.message,true)
|
||||
}
|
||||
var foundDevice = false
|
||||
var errorMessage = ''
|
||||
switch(true){
|
||||
//ONVIF camera found but denied access
|
||||
case searchError('400'): //Bad Request - Sender not Authorized
|
||||
foundDevice = true
|
||||
errorMessage = lang.ONVIFErr400
|
||||
break;
|
||||
case searchError('405'): //Method Not Allowed
|
||||
foundDevice = true
|
||||
errorMessage = lang.ONVIFErr405
|
||||
break;
|
||||
//Webserver exists but undetermined if IP Camera
|
||||
case searchError('404'): //Not Found
|
||||
foundDevice = true
|
||||
errorMessage = lang.ONVIFErr404
|
||||
break;
|
||||
}
|
||||
if(foundDevice && foundCameraCallback){
|
||||
foundCameraCallback({
|
||||
f: 'onvif',
|
||||
ff: 'failed_capture',
|
||||
ip: camera.ip,
|
||||
port: camera.port,
|
||||
error: errorMessage
|
||||
})
|
||||
}
|
||||
if(config.debugLogVerbose)s.debugLog(err);
|
||||
}
|
||||
})
|
||||
return responseList
|
||||
}
|
||||
|
||||
return {
|
||||
expandIPRange,
|
||||
ipRange,
|
||||
portRange,
|
||||
scanStatus,
|
||||
runOnvifScanner,
|
||||
stopOnvifScanner,
|
||||
};
|
||||
ipRange: ipRange,
|
||||
portRange: portRange,
|
||||
runOnvifScanner: runOnvifScanner,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ $(document).ready(function(e){
|
|||
var loadedResultsByIp = {}
|
||||
var monitorEditorWindow = $('#tab-monitorSettings')
|
||||
var onvifScannerWindow = $('#tab-onvifScanner')
|
||||
var onvifScannerStartButton = onvifScannerWindow.find('.start-scan')
|
||||
var onvifScannerStopButton = onvifScannerWindow.find('.stop-scan')
|
||||
var onvifScannerResultPane = onvifScannerWindow.find('.onvif_result')
|
||||
var onvifScannerErrorResultPane = onvifScannerWindow.find('.onvif_result_error')
|
||||
var scanForm = onvifScannerWindow.find('form');
|
||||
|
@ -32,97 +30,86 @@ $(document).ready(function(e){
|
|||
var html = buildSubMenuItems(allFound)
|
||||
sideMenuList.html(html)
|
||||
}
|
||||
var showStopButton = function(appearance){
|
||||
var setAsLoading = function(appearance){
|
||||
if(appearance){
|
||||
onvifScannerStartButton.addClass('d-none')
|
||||
onvifScannerStopButton.removeClass('d-none')
|
||||
onvifScannerWindow.find('._loading').show()
|
||||
onvifScannerWindow.find('[type="submit"]').prop('disabled',true)
|
||||
}else{
|
||||
onvifScannerStartButton.removeClass('d-none')
|
||||
onvifScannerStopButton.addClass('d-none')
|
||||
onvifScannerWindow.find('._loading').hide()
|
||||
onvifScannerWindow.find('[type="submit"]').prop('disabled',false)
|
||||
}
|
||||
}
|
||||
|
||||
function drawDeviceTableRow(device, gotAccess){
|
||||
var ip = device.ip;
|
||||
var el = onvifScannerResultPane.find(`[scan-item="${ip}"]`)
|
||||
var hasError = !!device.error;
|
||||
var uriText = !hasError ? device.uri ? device.uri.split('?')[0] : '' : device.error;
|
||||
var statusColor = hasError ? 'red' : 'green';
|
||||
var snapShot = device.snapShot;
|
||||
// console.log(ip, device.error, hasError)
|
||||
if(gotAccess)loadMonitorConfigFromResult(device)
|
||||
if(el.length === 0){
|
||||
var html = `<div scan-item="${ip}" class="col-md-6">
|
||||
<div class="card btn-default d-flex flex-row align-items-center p-2 mb-3 mx-2" style="border:none;border-left: 3px solid;border-color: ${statusColor}">
|
||||
<div class="pr-2"><i class="fa fa-square" style="color:${statusColor}"></i></div>
|
||||
<div class="pr-2"><div class="scan-item-img copy ${snapShot ? `cursor-pointer` : ''}" style="${snapShot ? `background-image:url(data:image/jpeg;base64,${snapShot})` : 'background-color:${statusColor};'}"></div></div>
|
||||
<div class="pr-2 flex-grow-1">${ip}<br><small class="uri">${uriText}</small></div>
|
||||
<div class="text-center copy-button pr-2">${!hasError ? makeButton({text: lang.Copy, class:'copy', color: 'primary'}) : ''}</div>
|
||||
</div>
|
||||
</div>`
|
||||
onvifScannerResultPane.append(html)
|
||||
function drawProbeResult(options){
|
||||
if(!options.error){
|
||||
var currentUsername = onvifScannerWindow.find('[name="user"]').val()
|
||||
var currentPassword = onvifScannerWindow.find('[name="pass"]').val()
|
||||
var tempID = generateId()
|
||||
var info = options.info ? jsonToHtmlBlock(options.info) : ''
|
||||
var streamUrl = ''
|
||||
var launchWebPage = `target="_blank" href="http${options.port == 443 ? 's' : ''}://${options.ip}:${options.port}"`
|
||||
if(options.uri){
|
||||
streamUrl = options.uri
|
||||
}
|
||||
var theLocation = getLocationFromUri(options.uri)
|
||||
var pathLocation = theLocation.location
|
||||
var monitorConfigPartial = {
|
||||
name: pathLocation.hostname,
|
||||
mid: tempID + `${options.port}`,
|
||||
host: pathLocation.hostname,
|
||||
port: pathLocation.port,
|
||||
path: pathLocation.pathname + (pathLocation.search && pathLocation.search !== '?' ? pathLocation.search : ''),
|
||||
protocol: theLocation.protocol,
|
||||
details: {
|
||||
auto_host: addCredentialsToUri(streamUrl,currentUsername,currentPassword),
|
||||
muser: currentUsername,
|
||||
mpass: currentPassword,
|
||||
is_onvif: '1',
|
||||
onvif_port: options.port,
|
||||
},
|
||||
}
|
||||
if(options.isPTZ){
|
||||
monitorConfigPartial.details = Object.assign(monitorConfigPartial.details,{
|
||||
control: '1',
|
||||
control_url_method: 'ONVIF',
|
||||
control_stop: '1',
|
||||
})
|
||||
}
|
||||
var monitorAlreadyAdded = isOnvifRowAlreadyALoadedMonitor(monitorConfigPartial)
|
||||
if(monitorAlreadyAdded){
|
||||
monitorConfigPartial.mid = monitorAlreadyAdded.mid;
|
||||
}
|
||||
var monitorId = monitorConfigPartial.mid
|
||||
loadedResults[monitorId] = monitorConfigPartial;
|
||||
loadedResultsByIp[monitorConfigPartial.host] = monitorConfigPartial;
|
||||
onvifScannerResultPane.append(`
|
||||
<div class="col-md-4 mb-3" onvif_row="${monitorId}" id="onvif-result-${monitorId}">
|
||||
<div style="display:block" class="card shadow btn-default copy">
|
||||
<div class="preview-image card-header" style="background-image:url(${options.snapShot ? 'data:image/png;base64,' + options.snapShot : placeholder.getData(placeholder.plcimg({text: ' ', fsize: 25, bgcolor:'#1f80f9'}))})"></div>
|
||||
<div class="card-body" style="min-height:190px">
|
||||
<div>${info}</div>
|
||||
<div class="url">${streamUrl}</div>
|
||||
</div>
|
||||
<div class="card-footer">${options.ip}:${options.port}</div>
|
||||
</div>
|
||||
</div>
|
||||
`)
|
||||
onvifScannerWindow.find('._notfound').remove()
|
||||
setAsLoading(false)
|
||||
drawFoundCamerasSubMenu()
|
||||
}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(!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>
|
||||
`)
|
||||
}
|
||||
if(snapShot){
|
||||
imgEl.css('background-image', `url("data:image/jpeg;base64,${snapShot}")`)
|
||||
}else{
|
||||
imgEl.css('background-image', '')
|
||||
}
|
||||
imgEl.css('background-color', statusColor)
|
||||
el.find('.uri').text(uriText)
|
||||
el.find('.card').css('border-color', statusColor)
|
||||
el.find('.fa-circle').css('color', statusColor)
|
||||
}
|
||||
}
|
||||
function loadMonitorConfigFromResult(options){
|
||||
var monitorId = removeSpecialCharacters(options.ip)
|
||||
var currentUsername = options.user
|
||||
var currentPassword = options.pass
|
||||
var streamUrl = ''
|
||||
var launchWebPage = `target="_blank" href="http${options.port == 443 ? 's' : ''}://${options.ip}:${options.port}"`
|
||||
if(options.uri){
|
||||
streamUrl = options.uri
|
||||
}
|
||||
var theLocation = getLocationFromUri(options.uri)
|
||||
var pathLocation = theLocation.location
|
||||
var monitorConfigPartial = {
|
||||
name: pathLocation.hostname,
|
||||
mid: monitorId,
|
||||
host: pathLocation.hostname,
|
||||
port: pathLocation.port,
|
||||
path: pathLocation.pathname + (pathLocation.search && pathLocation.search !== '?' ? pathLocation.search : ''),
|
||||
protocol: theLocation.protocol,
|
||||
details: {
|
||||
auto_host: addCredentialsToUri(streamUrl,currentUsername,currentPassword),
|
||||
muser: currentUsername,
|
||||
mpass: currentPassword,
|
||||
is_onvif: '1',
|
||||
onvif_port: options.port,
|
||||
},
|
||||
}
|
||||
if(options.isPTZ){
|
||||
monitorConfigPartial.details = Object.assign(monitorConfigPartial.details,{
|
||||
control: '1',
|
||||
control_url_method: 'ONVIF',
|
||||
control_stop: '1',
|
||||
})
|
||||
}
|
||||
var monitorAlreadyAdded = isOnvifRowAlreadyALoadedMonitor(monitorConfigPartial)
|
||||
if(monitorAlreadyAdded){
|
||||
monitorConfigPartial.mid = monitorAlreadyAdded.mid;
|
||||
}
|
||||
loadedResults[monitorId] = monitorConfigPartial;
|
||||
loadedResultsByIp[monitorConfigPartial.host] = monitorConfigPartial;
|
||||
return monitorConfigPartial
|
||||
}
|
||||
function isOnvifRowAlreadyALoadedMonitor(onvifRow){
|
||||
var matches = null;
|
||||
$.each(loadedMonitors,function(n,monitor){
|
||||
|
@ -187,7 +174,7 @@ $(document).ready(function(e){
|
|||
var form = el.serializeObject();
|
||||
onvifScannerResultPane.empty();
|
||||
onvifScannerErrorResultPane.empty();
|
||||
showStopButton(true)
|
||||
setAsLoading(true)
|
||||
mainSocket.f({
|
||||
f: 'onvif',
|
||||
ip: form.ip,
|
||||
|
@ -197,80 +184,34 @@ $(document).ready(function(e){
|
|||
});
|
||||
clearTimeout(checkTimeout)
|
||||
checkTimeout = setTimeout(function(){
|
||||
if(onvifScannerResultPane.find('[scan-item]').length === 0){
|
||||
showStopButton(false)
|
||||
if(onvifScannerResultPane.find('.card').length === 0){
|
||||
setAsLoading(false)
|
||||
onvifScannerResultPane.append(`<div class="p-2 text-center ${definitions.Theme.isDark ? 'text-white' : ''} _notfound text-white epic-text">${lang.sorryNothingWasFound}</div>`)
|
||||
}
|
||||
},5000)
|
||||
return false;
|
||||
});
|
||||
onvifScannerWindow.on('click','.copy',function(e){
|
||||
e.preventDefault()
|
||||
onvifScannerWindow.on('click','.copy',function(){
|
||||
openMonitorEditorPage()
|
||||
var el = $(this).parents('[scan-item]');
|
||||
var id = el.attr('scan-item');
|
||||
var onvifRecord = loadedResultsByIp[id];
|
||||
var el = $(this).parents('[onvif_row]');
|
||||
var id = el.attr('onvif_row');
|
||||
var onvifRecord = loadedResults[id];
|
||||
var streamURL = onvifRecord.details.auto_host
|
||||
writeToMonitorSettingsWindow(onvifRecord)
|
||||
})
|
||||
onvifScannerWindow.on('click','.add-all',function(){
|
||||
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){
|
||||
// console.log(camera)
|
||||
postMonitor(camera)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
$.each(importableCameras,function(n,camera){
|
||||
// console.log(camera)
|
||||
postMonitor(camera)
|
||||
})
|
||||
})
|
||||
})
|
||||
onvifScannerWindow.on('click','.stop-scan',function(){
|
||||
mainSocket.f({ f: 'onvif_stop' });
|
||||
})
|
||||
|
||||
loadLocalOptions()
|
||||
onInitWebsocket(function (){
|
||||
mainSocket.f({ f: 'onvif_scan_reconnect' });
|
||||
})
|
||||
onWebSocketEvent(function (d){
|
||||
switch(d.f){
|
||||
case'onvif':
|
||||
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)
|
||||
drawProbeResult(d)
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue