Merge branch 'dev' into 'dev'
# Conflicts: # plugins/tensorflow-coral/detect_image.pymerge-requests/289/head
commit
e8fdfcc9f5
|
|
@ -4719,7 +4719,7 @@ module.exports = function(s,config,lang){
|
|||
}
|
||||
}
|
||||
},
|
||||
"ONVIF Device Manager": {
|
||||
"ONVIF Device Manager": {
|
||||
"section": "ONVIF Device Manager",
|
||||
"blocks": {
|
||||
"Notice": {
|
||||
|
|
@ -5186,6 +5186,175 @@ module.exports = function(s,config,lang){
|
|||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"Sub-Account Manager": {
|
||||
"section": "Sub-Account Manager",
|
||||
"blocks": {
|
||||
"Sub-Accounts": {
|
||||
"name": lang['Sub-Accounts'],
|
||||
"color": "grey",
|
||||
"isSection": true,
|
||||
"id":"monSectionAccountList",
|
||||
"info": [
|
||||
{
|
||||
"fieldType": "table",
|
||||
id: "subAccountsList",
|
||||
}
|
||||
]
|
||||
},
|
||||
"Account Information": {
|
||||
"name": lang['Account Information'],
|
||||
"color": "grey",
|
||||
"isSection": true,
|
||||
"isForm": true,
|
||||
"id":"monSectionAccountInformation",
|
||||
"info": [
|
||||
{
|
||||
hidden: true,
|
||||
"name": "uid",
|
||||
"field": "UID",
|
||||
"fieldType": "text"
|
||||
},
|
||||
{
|
||||
"name": "mail",
|
||||
"field": lang.Email,
|
||||
"fieldType": "text",
|
||||
"default": "",
|
||||
"possible": ""
|
||||
},
|
||||
{
|
||||
"name": "pass",
|
||||
"field": lang.Password,
|
||||
"fieldType": "password",
|
||||
"default": "",
|
||||
"possible": ""
|
||||
},
|
||||
{
|
||||
"name": "password_again",
|
||||
"field": lang['Password Again'],
|
||||
"fieldType": "password",
|
||||
"default": "",
|
||||
"possible": ""
|
||||
},
|
||||
{
|
||||
forForm: true,
|
||||
"fieldType": "btn",
|
||||
"attribute": `type="reset"`,
|
||||
"class": `btn-default reset-form`,
|
||||
"btnContent": `<i class="fa fa-undo"></i> ${lang['Clear']}`,
|
||||
},
|
||||
{
|
||||
"fieldType": "btn",
|
||||
"class": `btn-success submit-form`,
|
||||
"btnContent": `<i class="fa fa-plus"></i> ${lang['Add New']}`,
|
||||
},
|
||||
{
|
||||
hidden: true,
|
||||
"name": "details",
|
||||
"preFill": "{}",
|
||||
},
|
||||
]
|
||||
},
|
||||
"Account Privileges": {
|
||||
"name": lang['Account Privileges'],
|
||||
"color": "grey",
|
||||
"isSection": true,
|
||||
"id":"monSectionAccountPrivileges",
|
||||
"info": [
|
||||
{
|
||||
"name": "detail=allmonitors",
|
||||
"field": lang['All Monitors and Privileges'],
|
||||
"default": "0",
|
||||
"fieldType": "select",
|
||||
"selector": "h_perm_allmonitors",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "detail=monitor_create",
|
||||
"field": lang['Can Create and Delete Monitors'],
|
||||
"default": "0",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "detail=user_change",
|
||||
"field": lang['Can Change User Settings'],
|
||||
"default": "0",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "detail=view_logs",
|
||||
"field": lang['Can View Logs'],
|
||||
"default": "0",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.No,
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"name": lang.Yes,
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "detail=landing_page",
|
||||
"field": lang['Landing Page'],
|
||||
"default": "",
|
||||
"fieldType": "select",
|
||||
"possible": [
|
||||
{
|
||||
"name": lang.Default,
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"name": lang.Timelapse,
|
||||
"value": "timelapse"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"fieldType": "div",
|
||||
"class": "h_perm_allmonitors_input h_perm_allmonitors_1",
|
||||
id: "sub_accounts_permissions",
|
||||
},
|
||||
{
|
||||
"fieldType": "btn",
|
||||
"class": `btn-success submit-form`,
|
||||
"btnContent": `<i class="fa fa-plus"></i> ${lang['Add New']}`,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,19 @@
|
|||
"failedLoginText2": "Please check your login credentials.",
|
||||
"Time Left": "Time Left",
|
||||
"Inverse Trigger": "Inverse Trigger",
|
||||
"Account Privileges": "Account Privileges",
|
||||
"Account Information": "Account Information",
|
||||
"subAccountManager": "Sub-Account Manager",
|
||||
"contactAdmin": "Contact the maintainer of your Shinobi installation.",
|
||||
"accountAdded": "Account Added",
|
||||
"accountAddedText": "Account has been added.",
|
||||
"accountDeleted": "Account Deleted",
|
||||
"accountDeletedText": "Account has been deleted.",
|
||||
"accountActionFailed": "Account Action Failed",
|
||||
"deleteSubAccount": "Delete Sub-Account",
|
||||
"deleteSubAccountText": "Do you want to delete this Sub-Account? You cannot recover it.",
|
||||
"Turn Speed": "Turn Speed",
|
||||
"Session Key": "Session Key",
|
||||
"Login": "Login",
|
||||
"Authenticate": "Authenticate",
|
||||
"Dashboard": "Dashboard",
|
||||
|
|
@ -171,6 +183,8 @@
|
|||
"Started Building": "Started Building",
|
||||
"Add": "Add",
|
||||
"Save": "Save",
|
||||
"Save New": "Save New",
|
||||
"Save Changes": "Save Changes",
|
||||
"Close": "Close",
|
||||
"Don't Show for 1 Week": "Don't Show for 1 Week",
|
||||
"Secure": "Secure",
|
||||
|
|
@ -451,6 +465,7 @@
|
|||
"NoVideosFoundForDateRange": "No Videos found in this date range. Try setting the start date further back.",
|
||||
"NoLogsFoundForDateRange": "No Logs found in this date range. Try widening the date range.",
|
||||
"monitorEditFailedMaxReached": "Your account has reached the maximum number of cameras that can be created. Speak to an administrator if you would like this changed.",
|
||||
"Sub-Accounts": "Sub-Accounts",
|
||||
"in": "in",
|
||||
"ago": "ago",
|
||||
"a few seconds": "a few seconds",
|
||||
|
|
@ -502,6 +517,7 @@
|
|||
"Detector Recording Process Exited Prematurely. Restarting.": "Detector Recording Process Exited Prematurely. Restarting.",
|
||||
"Detector Recording Complete": "Detector Recording Complete",
|
||||
"Clear Recorder Process": "Clear Recorder Process",
|
||||
"Clear": "Clear",
|
||||
"Logging": "Logging",
|
||||
"Timelapse": "Timelapse",
|
||||
"Nothing exists": "Nothing exists",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
var fs = require('fs')
|
||||
var P2P = require('pipe2pam')
|
||||
var PamDiff = require('pam-diff')
|
||||
module.exports = function(jsonData,pamDiffResponder){
|
||||
|
|
@ -50,7 +51,7 @@ module.exports = function(jsonData,pamDiffResponder){
|
|||
}
|
||||
|
||||
globalThreshold = parseInt(jsonData.rawMonitorConfig.details.detector_threshold) || 0
|
||||
|
||||
const regionsAreMasks = jsonData.rawMonitorConfig.details.detector_frame !== '1' && jsonData.rawMonitorConfig.details.inverse_trigger === '1';
|
||||
var regionJson
|
||||
try{
|
||||
regionJson = JSON.parse(jsonData.rawMonitorConfig.details.cords)
|
||||
|
|
@ -61,8 +62,8 @@ module.exports = function(jsonData,pamDiffResponder){
|
|||
if(Object.keys(regionJson).length === 0 || jsonData.rawMonitorConfig.details.detector_frame === '1'){
|
||||
fullFrame = {
|
||||
name:'FULL_FRAME',
|
||||
sensitivity:globalSensitivity,
|
||||
color_threshold:globalColorThreshold,
|
||||
sensitivity: globalSensitivity,
|
||||
color_threshold: globalColorThreshold,
|
||||
points:[
|
||||
[0,0],
|
||||
[0,height],
|
||||
|
|
@ -72,12 +73,18 @@ module.exports = function(jsonData,pamDiffResponder){
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const mask = {
|
||||
max_sensitivity : globalSensitivity,
|
||||
threshold : globalThreshold,
|
||||
}
|
||||
var regions = createPamDiffRegionArray(regionJson,globalColorThreshold,globalSensitivity,fullFrame)
|
||||
var pamDiffOptions = {
|
||||
mask: jsonData.rawMonitorConfig.details.detector_frame !== '1' && jsonData.rawMonitorConfig.details.inverse_trigger === '1',
|
||||
mask: regionsAreMasks,
|
||||
grayscale: 'luminosity',
|
||||
regions : regions.forPam
|
||||
regions : regions.forPam,
|
||||
percent : globalSensitivity,
|
||||
difference : globalColorThreshold,
|
||||
|
||||
}
|
||||
if(jsonData.rawMonitorConfig.details.detector_show_matrix==='1'){
|
||||
pamDiffOptions.response = 'bounds'
|
||||
|
|
@ -108,7 +115,7 @@ module.exports = function(jsonData,pamDiffResponder){
|
|||
var filteredCount = 0
|
||||
var filteredCountSuccess = 0
|
||||
trigger.merged.forEach(function(triggerPiece){
|
||||
var region = regionArray.find(x => x.name == triggerPiece.name)
|
||||
var region = regionsAreMasks ? mask : regionArray.find(x => x.name == triggerPiece.name)
|
||||
checkMaximumSensitivity(region, detectorObject, function(err1) {
|
||||
checkTriggerThreshold(region, detectorObject, function(err2) {
|
||||
++filteredCount
|
||||
|
|
@ -122,7 +129,7 @@ module.exports = function(jsonData,pamDiffResponder){
|
|||
})
|
||||
}else{
|
||||
if(trigger.matrix)detectorObject.details.matrices = [trigger.matrix]
|
||||
var region = regionArray.find(x => x.name == detectorObject.name)
|
||||
var region = regionsAreMasks ? mask : regionArray.find(x => x.name == detectorObject.name)
|
||||
checkMaximumSensitivity(region, detectorObject, function(err1) {
|
||||
checkTriggerThreshold(region, detectorObject, function(err2) {
|
||||
if(!err1 && !err2){
|
||||
|
|
@ -175,7 +182,7 @@ module.exports = function(jsonData,pamDiffResponder){
|
|||
imgWidth:jsonData.rawMonitorConfig.details.detector_scale_x
|
||||
}
|
||||
if(trigger.matrix)detectorObject.details.matrices = [trigger.matrix]
|
||||
var region = Object.values(regionJson).find(x => x.name == detectorObject.name)
|
||||
var region = regionsAreMasks ? mask : Object.values(regionJson).find(x => x.name == detectorObject.name)
|
||||
checkMaximumSensitivity(region, detectorObject, function(err1) {
|
||||
checkTriggerThreshold(region, detectorObject, function(err2) {
|
||||
if(!err1 && ! err2){
|
||||
|
|
|
|||
|
|
@ -83,12 +83,12 @@ module.exports = function(s,config,lang){
|
|||
})
|
||||
}else{
|
||||
var onvifDirections = {
|
||||
"left": [-1.0,'x'],
|
||||
"right": [1.0,'x'],
|
||||
"down": [invertedVerticalAxis ? 1.0 : -1.0,'y'],
|
||||
"up": [invertedVerticalAxis ? -1.0 : 1.0,'y'],
|
||||
"zoom_in": [1.0,'z'],
|
||||
"zoom_out": [-1.0,'z']
|
||||
"left": [-turnSpeed,'x'],
|
||||
"right": [turnSpeed,'x'],
|
||||
"down": [invertedVerticalAxis ? turnSpeed : -turnSpeed,'y'],
|
||||
"up": [invertedVerticalAxis ? -turnSpeed : turnSpeed,'y'],
|
||||
"zoom_in": [turnSpeed,'z'],
|
||||
"zoom_out": [-turnSpeed,'z']
|
||||
}
|
||||
var direction = onvifDirections[options.direction]
|
||||
controlOptions.Velocity[direction[1]] = direction[0]
|
||||
|
|
|
|||
|
|
@ -403,11 +403,23 @@ module.exports = function(s,config){
|
|||
}
|
||||
})
|
||||
}
|
||||
const knexQueryPromise = (options) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
knexQuery(options,(err,rows) => {
|
||||
resolve({
|
||||
ok: !err,
|
||||
err: err,
|
||||
rows: rows,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
const connectDatabase = function(){
|
||||
s.databaseEngine = require('knex')(s.databaseOptions)
|
||||
}
|
||||
return {
|
||||
knexQuery: knexQuery,
|
||||
knexQueryPromise: knexQueryPromise,
|
||||
knexError: knexError,
|
||||
cleanSqlWhereObject: cleanSqlWhereObject,
|
||||
processSimpleWhereCondition: processSimpleWhereCondition,
|
||||
|
|
|
|||
|
|
@ -372,111 +372,113 @@ module.exports = (s,config,lang) => {
|
|||
const buildMainStream = function(e){
|
||||
//e = monitor object
|
||||
//x = temporary values
|
||||
const isCudaEnabled = hasCudaEnabled(e)
|
||||
const streamFlags = []
|
||||
const streamFilters = []
|
||||
const videoCodecisCopy = e.details.stream_vcodec === 'copy'
|
||||
const videoCodec = e.details.stream_vcodec ? e.details.stream_vcodec : 'no'
|
||||
const audioCodec = e.details.stream_acodec ? e.details.stream_acodec : 'no'
|
||||
const videoQuality = e.details.stream_quality ? e.details.stream_quality : '1'
|
||||
const streamType = e.details.stream_type ? e.details.stream_type : 'hls'
|
||||
const videoFps = !isNaN(parseFloat(e.details.stream_fps)) && e.details.stream_fps !== '0' ? parseFloat(e.details.stream_fps) : null
|
||||
const inputMap = buildInputMap(e,e.details.input_map_choices.stream)
|
||||
const outputCanHaveAudio = (streamType === 'hls' || streamType === 'mp4' || streamType === 'flv' || streamType === 'h265')
|
||||
const outputRequiresEncoding = streamType === 'mjpeg' || streamType === 'b64'
|
||||
const outputIsPresetCapable = outputCanHaveAudio
|
||||
const { videoWidth, videoHeight } = validateDimensions(e.details.stream_scale_x,e.details.stream_scale_y)
|
||||
if(inputMap)streamFlags.push(inputMap)
|
||||
if(e.details.cust_stream)streamFlags.push(e.details.cust_stream)
|
||||
if(streamFlags.indexOf('-strict -2') === -1)streamFlags.push(`-strict -2`)
|
||||
//stream - timestamp
|
||||
if(e.details.stream_timestamp === "1" && !videoCodecisCopy){
|
||||
streamFilters.push(buildTimestampFiltersFromConfiguration('stream_',e))
|
||||
}
|
||||
if(e.details.stream_watermark === "1" && e.details.stream_watermark_location){
|
||||
streamFilters.push(buildWatermarkFiltersFromConfiguration(`stream_`,e))
|
||||
}
|
||||
//stream - rotation
|
||||
if(e.details.stream_rotate && e.details.stream_rotate !== "no" && e.details.stream_vcodec !== 'copy'){
|
||||
streamFilters.push(buildRotationFiltersFromConfiguration(`stream_`,e))
|
||||
}
|
||||
if(outputCanHaveAudio && audioCodec !== 'no'){
|
||||
streamFlags.push(`-c:a ` + audioCodec)
|
||||
}else{
|
||||
streamFlags.push(`-an`)
|
||||
}
|
||||
if(videoCodec === 'h264_vaapi'){
|
||||
streamFilters.push('format=nv12,hwupload');
|
||||
if(e.details.stream_scale_x && e.details.stream_scale_y){
|
||||
streamFilters.push('scale_vaapi=w='+e.details.stream_scale_x+':h='+e.details.stream_scale_y)
|
||||
if(streamType !== 'jpeg'){
|
||||
const isCudaEnabled = hasCudaEnabled(e)
|
||||
const streamFilters = []
|
||||
const videoCodecisCopy = e.details.stream_vcodec === 'copy'
|
||||
const videoCodec = e.details.stream_vcodec ? e.details.stream_vcodec : 'no'
|
||||
const audioCodec = e.details.stream_acodec ? e.details.stream_acodec : 'no'
|
||||
const videoQuality = e.details.stream_quality ? e.details.stream_quality : '1'
|
||||
const videoFps = !isNaN(parseFloat(e.details.stream_fps)) && e.details.stream_fps !== '0' ? parseFloat(e.details.stream_fps) : null
|
||||
const inputMap = buildInputMap(e,e.details.input_map_choices.stream)
|
||||
const outputCanHaveAudio = (streamType === 'hls' || streamType === 'mp4' || streamType === 'flv' || streamType === 'h265')
|
||||
const outputRequiresEncoding = streamType === 'mjpeg' || streamType === 'b64'
|
||||
const outputIsPresetCapable = outputCanHaveAudio
|
||||
const { videoWidth, videoHeight } = validateDimensions(e.details.stream_scale_x,e.details.stream_scale_y)
|
||||
if(inputMap)streamFlags.push(inputMap)
|
||||
if(e.details.cust_stream)streamFlags.push(e.details.cust_stream)
|
||||
if(streamFlags.indexOf('-strict -2') === -1)streamFlags.push(`-strict -2`)
|
||||
//stream - timestamp
|
||||
if(e.details.stream_timestamp === "1" && !videoCodecisCopy){
|
||||
streamFilters.push(buildTimestampFiltersFromConfiguration('stream_',e))
|
||||
}
|
||||
}
|
||||
if(isCudaEnabled && (streamType === 'mjpeg' || streamType === 'b64')){
|
||||
streamFilters.push('hwdownload,format=nv12')
|
||||
}
|
||||
if(!outputRequiresEncoding && videoCodec !== 'no'){
|
||||
streamFlags.push(`-c:v ` + videoCodec)
|
||||
}
|
||||
if(!videoCodecisCopy || outputRequiresEncoding){
|
||||
if(videoWidth && videoHeight)streamFlags.push(`-s ${videoWidth}x${videoHeight}`)
|
||||
if(videoFps && streamType === 'mjpeg' || streamType === 'b64'){
|
||||
streamFilters.push(`fps=${videoFps}`)
|
||||
if(e.details.stream_watermark === "1" && e.details.stream_watermark_location){
|
||||
streamFilters.push(buildWatermarkFiltersFromConfiguration(`stream_`,e))
|
||||
}
|
||||
}
|
||||
if(e.details.stream_vf){
|
||||
streamFilters.push(e.details.stream_vf)
|
||||
}
|
||||
if(outputIsPresetCapable){
|
||||
const streamPreset = streamType !== 'h265' && e.details.preset_stream ? e.details.preset_stream : null
|
||||
if(streamPreset){
|
||||
streamFlags.push(`-preset ${streamPreset}`)
|
||||
//stream - rotation
|
||||
if(e.details.stream_rotate && e.details.stream_rotate !== "no" && e.details.stream_vcodec !== 'copy'){
|
||||
streamFilters.push(buildRotationFiltersFromConfiguration(`stream_`,e))
|
||||
}
|
||||
if(!videoCodecisCopy){
|
||||
streamFlags.push(`-crf ${videoQuality}`)
|
||||
if(outputCanHaveAudio && audioCodec !== 'no'){
|
||||
streamFlags.push(`-c:a ` + audioCodec)
|
||||
}else{
|
||||
streamFlags.push(`-an`)
|
||||
}
|
||||
}else{
|
||||
streamFlags.push(`-q:v ${videoQuality}`)
|
||||
}
|
||||
if((!videoCodecisCopy || outputRequiresEncoding) && streamFilters.length > 0){
|
||||
streamFlags.push(`-vf "${streamFilters.join(',')}"`)
|
||||
}
|
||||
switch(streamType){
|
||||
case'mp4':
|
||||
streamFlags.push('-f mp4 -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream from Shinobi" -reset_timestamps 1 pipe:1')
|
||||
break;
|
||||
case'flv':
|
||||
streamFlags.push(`-f flv`,'pipe:1')
|
||||
break;
|
||||
case'hls':
|
||||
const hlsTime = !isNaN(parseInt(e.details.hls_time)) ? `${parseInt(e.details.hls_time)}` : '2'
|
||||
const hlsListSize = !isNaN(parseInt(e.details.hls_list_size)) ? `${parseInt(e.details.hls_list_size)}` : '2'
|
||||
if(videoCodec !== 'h264_vaapi' && !videoCodecisCopy){
|
||||
if(!arrayContains('-tune',streamFlags)){
|
||||
streamFlags.push(`-tune zerolatency`)
|
||||
}
|
||||
if(!arrayContains('-g ',streamFlags)){
|
||||
streamFlags.push(`-g 1`)
|
||||
}
|
||||
if(videoCodec === 'h264_vaapi'){
|
||||
streamFilters.push('format=nv12,hwupload');
|
||||
if(e.details.stream_scale_x && e.details.stream_scale_y){
|
||||
streamFilters.push('scale_vaapi=w='+e.details.stream_scale_x+':h='+e.details.stream_scale_y)
|
||||
}
|
||||
streamFlags.push(`-f hls -hls_time ${hlsTime} -hls_list_size ${hlsListSize} -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "${e.sdir}s.m3u8"`)
|
||||
break;
|
||||
case'mjpeg':
|
||||
streamFlags.push(`-an -c:v mjpeg -f mpjpeg -boundary_tag shinobi pipe:1`)
|
||||
break;
|
||||
case'h265':
|
||||
streamFlags.push(`-movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Shinobi H.265 Stream" -reset_timestamps 1 -f hevc pipe:1`)
|
||||
break;
|
||||
case'b64':case'':case undefined:case null://base64
|
||||
streamFlags.push(`-an -c:v mjpeg -f image2pipe pipe:1`)
|
||||
break;
|
||||
}
|
||||
if(e.details.custom_output){
|
||||
streamFlags.push(e.details.custom_output)
|
||||
}
|
||||
if(e.details.stream_channels){
|
||||
e.details.stream_channels.forEach(function(v,n){
|
||||
streamFlags.push(createStreamChannel(e,n + config.pipeAddition,v))
|
||||
})
|
||||
}
|
||||
if(isCudaEnabled && (streamType === 'mjpeg' || streamType === 'b64')){
|
||||
streamFilters.push('hwdownload,format=nv12')
|
||||
}
|
||||
if(!outputRequiresEncoding && videoCodec !== 'no'){
|
||||
streamFlags.push(`-c:v ` + videoCodec)
|
||||
}
|
||||
if(!videoCodecisCopy || outputRequiresEncoding){
|
||||
if(videoWidth && videoHeight)streamFlags.push(`-s ${videoWidth}x${videoHeight}`)
|
||||
if(videoFps && streamType === 'mjpeg' || streamType === 'b64'){
|
||||
streamFilters.push(`fps=${videoFps}`)
|
||||
}
|
||||
}
|
||||
if(e.details.stream_vf){
|
||||
streamFilters.push(e.details.stream_vf)
|
||||
}
|
||||
if(outputIsPresetCapable){
|
||||
const streamPreset = streamType !== 'h265' && e.details.preset_stream ? e.details.preset_stream : null
|
||||
if(streamPreset){
|
||||
streamFlags.push(`-preset ${streamPreset}`)
|
||||
}
|
||||
if(!videoCodecisCopy){
|
||||
streamFlags.push(`-crf ${videoQuality}`)
|
||||
}
|
||||
}else{
|
||||
streamFlags.push(`-q:v ${videoQuality}`)
|
||||
}
|
||||
if((!videoCodecisCopy || outputRequiresEncoding) && streamFilters.length > 0){
|
||||
streamFlags.push(`-vf "${streamFilters.join(',')}"`)
|
||||
}
|
||||
switch(streamType){
|
||||
case'mp4':
|
||||
streamFlags.push('-f mp4 -movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Poseidon Stream from Shinobi" -reset_timestamps 1 pipe:1')
|
||||
break;
|
||||
case'flv':
|
||||
streamFlags.push(`-f flv`,'pipe:1')
|
||||
break;
|
||||
case'hls':
|
||||
const hlsTime = !isNaN(parseInt(e.details.hls_time)) ? `${parseInt(e.details.hls_time)}` : '2'
|
||||
const hlsListSize = !isNaN(parseInt(e.details.hls_list_size)) ? `${parseInt(e.details.hls_list_size)}` : '2'
|
||||
if(videoCodec !== 'h264_vaapi' && !videoCodecisCopy){
|
||||
if(!arrayContains('-tune',streamFlags)){
|
||||
streamFlags.push(`-tune zerolatency`)
|
||||
}
|
||||
if(!arrayContains('-g ',streamFlags)){
|
||||
streamFlags.push(`-g 1`)
|
||||
}
|
||||
}
|
||||
streamFlags.push(`-f hls -hls_time ${hlsTime} -hls_list_size ${hlsListSize} -start_number 0 -hls_allow_cache 0 -hls_flags +delete_segments+omit_endlist "${e.sdir}s.m3u8"`)
|
||||
break;
|
||||
case'mjpeg':
|
||||
streamFlags.push(`-an -c:v mjpeg -f mpjpeg -boundary_tag shinobi pipe:1`)
|
||||
break;
|
||||
case'h265':
|
||||
streamFlags.push(`-movflags +frag_keyframe+empty_moov+default_base_moof -metadata title="Shinobi H.265 Stream" -reset_timestamps 1 -f hevc pipe:1`)
|
||||
break;
|
||||
case'b64':case'':case undefined:case null://base64
|
||||
streamFlags.push(`-an -c:v mjpeg -f image2pipe pipe:1`)
|
||||
break;
|
||||
}
|
||||
if(e.details.custom_output){
|
||||
streamFlags.push(e.details.custom_output)
|
||||
}
|
||||
if(e.details.stream_channels){
|
||||
e.details.stream_channels.forEach(function(v,n){
|
||||
streamFlags.push(createStreamChannel(e,n + config.pipeAddition,v))
|
||||
})
|
||||
}
|
||||
}
|
||||
return streamFlags.join(' ')
|
||||
}
|
||||
|
|
@ -716,7 +718,7 @@ module.exports = (s,config,lang) => {
|
|||
}
|
||||
if(audioCodec === 'no'){
|
||||
outputFlags.push(`-an`)
|
||||
}else if(audioCodec){
|
||||
}else if(audioCodec && audioCodec !== 'auto'){
|
||||
outputFlags.push(`-c:a ` + audioCodec)
|
||||
}
|
||||
if(outputFilters.length > 0){
|
||||
|
|
|
|||
|
|
@ -166,8 +166,13 @@ module.exports = function(s,config,lang){
|
|||
screenShot: buffer,
|
||||
isStaticFile: false
|
||||
})
|
||||
fs.unlink(temporaryImageFile,function(){})
|
||||
}else{
|
||||
resolve({
|
||||
screenShot: null,
|
||||
isStaticFile: false
|
||||
})
|
||||
}
|
||||
fs.unlink(temporaryImageFile,function(){})
|
||||
})
|
||||
}
|
||||
try{
|
||||
|
|
@ -483,7 +488,7 @@ module.exports = function(s,config,lang){
|
|||
if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
|
||||
var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
|
||||
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
|
||||
if(screenShot && (screenShot[screenShot.length-2] === 0xFF && screenShot[screenShot.length-1] === 0xD9)){
|
||||
if(screenShot){
|
||||
s.tx({
|
||||
f: 'monitor_snapshot',
|
||||
snapshot: screenShot.toString('base64'),
|
||||
|
|
@ -513,7 +518,7 @@ module.exports = function(s,config,lang){
|
|||
if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
|
||||
var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
|
||||
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
|
||||
if(screenShot && (screenShot[screenShot.length-2] === 0xFF && screenShot[screenShot.length-1] === 0xD9)){
|
||||
if(screenShot){
|
||||
return screenShot
|
||||
}else{
|
||||
return await getDefaultImage()
|
||||
|
|
|
|||
|
|
@ -54,14 +54,13 @@ module.exports = function(s,config,lang){
|
|||
if(filter.discord && s.group[d.ke].discordBot && monitorConfig.details.detector_discordbot === '1' && !s.group[d.ke].activeMonitors[d.id].detector_discordbot){
|
||||
var detector_discordbot_timeout
|
||||
if(!monitorConfig.details.detector_discordbot_timeout||monitorConfig.details.detector_discordbot_timeout===''){
|
||||
detector_discordbot_timeout = 1000*60*10;
|
||||
detector_discordbot_timeout = 1000 * 60 * 10;
|
||||
}else{
|
||||
detector_discordbot_timeout = parseFloat(monitorConfig.details.detector_discordbot_timeout)*1000*60;
|
||||
detector_discordbot_timeout = parseFloat(monitorConfig.details.detector_discordbot_timeout) * 1000 * 60;
|
||||
}
|
||||
//lock mailer so you don't get emailed on EVERY trigger event.
|
||||
s.group[d.ke].activeMonitors[d.id].detector_discordbot = setTimeout(function(){
|
||||
clearTimeout(s.group[d.ke].activeMonitors[d.id].detector_discordbot);
|
||||
delete(s.group[d.ke].activeMonitors[d.id].detector_discordbot);
|
||||
s.group[d.ke].activeMonitors[d.id].detector_discordbot = null
|
||||
},detector_discordbot_timeout)
|
||||
if(monitorConfig.details.detector_discordbot_send_video === '1'){
|
||||
// change to function that captures on going video capture, waits, grabs new video file, slices portion (max for transmission) and prepares for delivery
|
||||
|
|
@ -89,7 +88,7 @@ module.exports = function(s,config,lang){
|
|||
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(monitorConfig,{
|
||||
secondsInward: monitorConfig.details.snap_seconds_inward
|
||||
})
|
||||
if(screenShot[screenShot.length - 2] === 0xFF && screenShot[screenShot.length - 1] === 0xD9){
|
||||
if(screenShot){
|
||||
d.screenshotBuffer = screenShot
|
||||
sendMessage({
|
||||
author: {
|
||||
|
|
|
|||
|
|
@ -113,15 +113,13 @@ module.exports = function(s,config,lang){
|
|||
r = r[0];
|
||||
var detector_mail_timeout
|
||||
if(!monitorConfig.details.detector_mail_timeout||monitorConfig.details.detector_mail_timeout===''){
|
||||
detector_mail_timeout = 1000*60*10;
|
||||
detector_mail_timeout = 1000 * 60 * 10;
|
||||
}else{
|
||||
detector_mail_timeout = parseFloat(monitorConfig.details.detector_mail_timeout)*1000*60;
|
||||
detector_mail_timeout = parseFloat(monitorConfig.details.detector_mail_timeout) * 1000 * 60;
|
||||
}
|
||||
//lock mailer so you don't get emailed on EVERY trigger event.
|
||||
s.group[d.ke].activeMonitors[d.id].detector_mail = setTimeout(function(){
|
||||
//unlock so you can mail again.
|
||||
clearTimeout(s.group[d.ke].activeMonitors[d.id].detector_mail);
|
||||
delete(s.group[d.ke].activeMonitors[d.id].detector_mail);
|
||||
s.group[d.ke].activeMonitors[d.id].detector_mail = null
|
||||
},detector_mail_timeout);
|
||||
const sendMail = function(files){
|
||||
const infoRows = []
|
||||
|
|
@ -183,7 +181,7 @@ module.exports = function(s,config,lang){
|
|||
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(monitorConfig,{
|
||||
secondsInward: monitorConfig.details.snap_seconds_inward
|
||||
})
|
||||
d.screenshotBuffer = screenShot
|
||||
if(screenShot)d.screenshotBuffer = screenShot
|
||||
}
|
||||
sendMail([
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ module.exports = function(s,config){
|
|||
}
|
||||
const {
|
||||
knexQuery,
|
||||
knexQueryPromise,
|
||||
knexError,
|
||||
cleanSqlWhereObject,
|
||||
processSimpleWhereCondition,
|
||||
|
|
@ -34,6 +35,7 @@ module.exports = function(s,config){
|
|||
extender(config)
|
||||
})
|
||||
s.knexQuery = knexQuery
|
||||
s.knexQueryPromise = knexQueryPromise
|
||||
s.getDatabaseRows = getDatabaseRows
|
||||
s.sqlQuery = sqlQuery
|
||||
s.connectDatabase = connectDatabase
|
||||
|
|
|
|||
|
|
@ -127,7 +127,8 @@ module.exports = function(s,config,lang,io){
|
|||
var userDetails = JSON.parse(user.details)
|
||||
s.group[user.ke].sizeLimit = parseFloat(userDetails.size) || 10000
|
||||
s.group[user.ke].sizeLimitVideoPercent = parseFloat(userDetails.size_video_percent) || 90
|
||||
s.group[user.ke].sizeLimitTimelapseFramesPercent = parseFloat(userDetails.size_timelapse_percent) || 10
|
||||
s.group[user.ke].sizeLimitTimelapseFramesPercent = parseFloat(userDetails.size_timelapse_percent) || 5
|
||||
s.group[user.ke].sizeLimitFileBinPercent = parseFloat(userDetails.size_filebin_percent) || 5
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
|
|
|
|||
|
|
@ -361,6 +361,7 @@ module.exports = function(s,config,lang){
|
|||
const value = form[key]
|
||||
updateQuery[key] = value
|
||||
})
|
||||
updateQuery.details = formDetails
|
||||
s.knexQuery({
|
||||
action: "update",
|
||||
table: "Users",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ module.exports = function(s,config,lang,app){
|
|||
* API : Administrator : Edit Sub-Account (Account to share cameras with)
|
||||
*/
|
||||
app.all(config.webPaths.adminApiPrefix+':auth/accounts/:ke/edit', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
s.auth(req.params,async (user) => {
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
|
|
@ -25,10 +25,35 @@ module.exports = function(s,config,lang,app){
|
|||
var mail = form.mail || s.getPostData(req,'mail',false)
|
||||
if(form){
|
||||
var keys = ['details']
|
||||
form.details = s.parseJSON(form.details) || {"sub": 1, "allmonitors": "1"}
|
||||
form.details.sub = 1
|
||||
const updateQuery = {
|
||||
details: s.stringJSON(form.details)
|
||||
}
|
||||
s.knexQuery({
|
||||
if(form.pass && form.pass === form.password_again){
|
||||
updateQuery.pass = s.createHash(form.pass)
|
||||
}
|
||||
if(form.mail){
|
||||
const userCheck = await s.knexQueryPromise({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Users",
|
||||
where: [
|
||||
['mail','=',form.mail],
|
||||
]
|
||||
})
|
||||
if(userCheck.rows[0]){
|
||||
const foundUser = userCheck.rows[0]
|
||||
if(foundUser.uid === form.uid){
|
||||
updateQuery.mail = form.mail
|
||||
}else{
|
||||
endData.msg = lang['Email address is in use.']
|
||||
s.closeJsonResponse(res,endData)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
await s.knexQueryPromise({
|
||||
action: "update",
|
||||
table: "Users",
|
||||
update: updateQuery,
|
||||
|
|
@ -125,6 +150,35 @@ module.exports = function(s,config,lang,app){
|
|||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Administrator : Get Sub-Account List
|
||||
*/
|
||||
app.get(config.webPaths.adminApiPrefix+':auth/accounts/:ke', function (req,res){
|
||||
s.auth(req.params,function(user){
|
||||
var endData = {
|
||||
ok : false
|
||||
}
|
||||
if(user.details.sub){
|
||||
endData.msg = user.lang['Not Permitted']
|
||||
s.closeJsonResponse(res,endData)
|
||||
return
|
||||
}else{
|
||||
endData.ok = true
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "ke,uid,mail,details",
|
||||
table: "Users",
|
||||
where: [
|
||||
['ke','=',req.params.ke],
|
||||
['details','LIKE','%"sub"%']
|
||||
]
|
||||
},function(err,rows){
|
||||
endData.accounts = rows
|
||||
s.closeJsonResponse(res,endData)
|
||||
})
|
||||
}
|
||||
},res,req)
|
||||
})
|
||||
/**
|
||||
* API : Administrator : Add Sub-Account (Account to share cameras with)
|
||||
*/
|
||||
app.post([
|
||||
|
|
@ -156,16 +210,17 @@ module.exports = function(s,config,lang,app){
|
|||
},function(err,r){
|
||||
if(r && r[0]){
|
||||
//found one exist
|
||||
endData.msg = 'Email address is in use.'
|
||||
endData.msg = lang['Email address is in use.']
|
||||
}else{
|
||||
//create new
|
||||
endData.msg = 'New Account Created'
|
||||
endData.ok = true
|
||||
var newId = s.gid()
|
||||
var details = s.s({
|
||||
sub: "1",
|
||||
var details = s.s(Object.assign({
|
||||
allmonitors: "1"
|
||||
})
|
||||
},s.parseJSON(form.details) || {
|
||||
sub: "1",
|
||||
}))
|
||||
s.knexQuery({
|
||||
action: "insert",
|
||||
table: "Users",
|
||||
|
|
|
|||
|
|
@ -171,9 +171,9 @@ module.exports = function(s,config,lang,app,io){
|
|||
return false
|
||||
}
|
||||
switch(true){
|
||||
case search(config.webPaths.admin):
|
||||
return 'admin'
|
||||
break;
|
||||
// case search(config.webPaths.admin):
|
||||
// return 'admin'
|
||||
// break;
|
||||
case search(config.webPaths.super):
|
||||
return 'super'
|
||||
break;
|
||||
|
|
@ -321,53 +321,6 @@ module.exports = function(s,config,lang,app,io){
|
|||
})
|
||||
break;
|
||||
case'admin':
|
||||
if(!r.details.sub){
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "uid,mail,details",
|
||||
table: "Users",
|
||||
where: [
|
||||
['ke','=',r.ke],
|
||||
['details','LIKE','%"sub"%'],
|
||||
]
|
||||
},(err,rr) => {
|
||||
s.knexQuery({
|
||||
action: "select",
|
||||
columns: "*",
|
||||
table: "Monitors",
|
||||
where: [
|
||||
['ke','=',r.ke],
|
||||
]
|
||||
},(err,rrr) => {
|
||||
renderPage(config.renderPaths.admin,{
|
||||
config: s.getConfigWithBranding(req.hostname),
|
||||
$user: response,
|
||||
$subs: rr,
|
||||
$mons: rrr,
|
||||
lang: r.lang,
|
||||
define: s.getDefinitonFile(r.details.lang),
|
||||
customAutoLoad: s.customAutoLoadTree
|
||||
})
|
||||
})
|
||||
})
|
||||
}else{
|
||||
//not admin user
|
||||
var chosenRender = 'home'
|
||||
if(r.details.landing_page && r.details.landing_page !== '' && config.renderPaths[r.details.landing_page]){
|
||||
chosenRender = r.details.landing_page
|
||||
}
|
||||
renderPage(config.renderPaths[chosenRender],{
|
||||
$user:response,
|
||||
config: s.getConfigWithBranding(req.hostname),
|
||||
lang:r.lang,
|
||||
define:s.getDefinitonFile(r.details.lang),
|
||||
addStorage:s.dir.addStorage,
|
||||
fs:fs,
|
||||
__dirname:s.mainDirectory,
|
||||
customAutoLoad: s.customAutoLoadTree
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
var chosenRender = 'home'
|
||||
if(r.details.sub && r.details.landing_page && r.details.landing_page !== '' && config.renderPaths[r.details.landing_page]){
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#!/bin/bash
|
||||
DIR=`dirname $0`
|
||||
DIR=$(dirname $0)
|
||||
rm -rf $DIR/node_modules
|
||||
if [ -x "$(command -v apt)" ]; then
|
||||
sudo apt update -y
|
||||
fi
|
||||
|
|
@ -94,9 +95,6 @@ echo "-----------------------------------"
|
|||
echo "Adding Random Plugin Key to Main Configuration"
|
||||
node $DIR/../../tools/modifyConfigurationForPlugin.js face key=$(head -c 64 < /dev/urandom | sha256sum | awk '{print substr($1,1,60)}') tfjsBuild=$tfjsBuildVal
|
||||
echo "-----------------------------------"
|
||||
echo "Updating Node Package Manager"
|
||||
sudo npm install npm -g --unsafe-perm
|
||||
echo "-----------------------------------"
|
||||
echo "Getting node-gyp to build C++ modules"
|
||||
if [ ! -x "$(command -v node-gyp)" ]; then
|
||||
# Check if Ubuntu
|
||||
|
|
@ -110,39 +108,35 @@ if [ ! -x "$(command -v node-gyp)" ]; then
|
|||
sudo yum install gcc-c++ cairo-devel libjpeg-turbo-devel pango-devel giflib-devel -y
|
||||
fi
|
||||
fi
|
||||
sudo npm install --unsafe-perm
|
||||
|
||||
sudo npm install node-gyp -g --unsafe-perm --force
|
||||
echo "-----------------------------------"
|
||||
npm uninstall @tensorflow/tfjs-node-gpu --unsafe-perm
|
||||
npm uninstall @tensorflow/tfjs-node --unsafe-perm
|
||||
echo "Getting C++ module : @tensorflow/tfjs-node@0.1.21"
|
||||
echo "https://github.com/tensorflow/tfjs-node"
|
||||
npm install @tensorflow/tfjs-core@1.7.3 --unsafe-perm --force
|
||||
npm install @tensorflow/tfjs-converter@1.7.3 --unsafe-perm --force
|
||||
npm install @tensorflow/tfjs-layers@1.7.3 --unsafe-perm --force
|
||||
echo "Getting C++ module : face-api.js"
|
||||
echo "https://github.com/justadudewhohacks/face-api.js"
|
||||
sudo npm install --unsafe-perm --force
|
||||
|
||||
# echo "Getting C++ module : @tensorflow/tfjs-node@0.1.21"
|
||||
# echo "https://github.com/tensorflow/tfjs-node"
|
||||
# npm install @tensorflow/tfjs-converter@1.7.4 @tensorflow/tfjs-layers@1.7.4 --unsafe-perm
|
||||
if [ "$INSTALL_WITH_GPU" = "1" ]; then
|
||||
echo "GPU version of tjfs : https://github.com/tensorflow/tfjs-node-gpu"
|
||||
else
|
||||
echo "CPU version of tjfs : https://github.com/tensorflow/tfjs-node"
|
||||
fi
|
||||
sudo npm install @tensorflow/tfjs-node$TFJS_SUFFIX@1.7.3 --unsafe-perm --force
|
||||
npm install @tensorflow/tfjs-node$TFJS_SUFFIX --unsafe-perm
|
||||
if [ "$INSTALL_FOR_ARM" = "1" ]; then
|
||||
cd node_modules/@tensorflow/tfjs-node$TFJS_SUFFIX
|
||||
BINARY_LOCATION="node_modules/@tensorflow/tfjs-node$TFJS_SUFFIX/scripts/custom-binary.json"
|
||||
if [ "$INSTALL_FOR_ARM64" = "1" ]; then
|
||||
echo "{
|
||||
\"tf-lib\": \"https://cdn.shinobi.video/binaries/libtensorflow-gpu-linux-arm64-1.15.0.tar.gz\"
|
||||
}" > scripts/custom-binary.json
|
||||
}" > $BINARY_LOCATION
|
||||
else
|
||||
echo "{
|
||||
\"tf-lib\": \"https://cdn.shinobi.video/binaries/libtensorflow-cpu-linux-arm-1.15.0.tar.gz\"
|
||||
}" > scripts/custom-binary.json
|
||||
}" > $BINARY_LOCATION
|
||||
fi
|
||||
npm install --unsafe-perm
|
||||
cd ../../..
|
||||
npm rebuild @tensorflow/tfjs-node$TFJS_SUFFIX --build-addon-from-source --unsafe-perm
|
||||
fi
|
||||
sudo npm audit fix --force
|
||||
rm -rf $DIR/node_modules/@tensorflow/tfjs-backend-cpu
|
||||
rm -rf $DIR/node_modules/@tensorflow/tfjs-backend-webgl
|
||||
echo "-----------------------------------"
|
||||
echo "Start the plugin with pm2 like so :"
|
||||
echo "pm2 start shinobi-face.js"
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -4,6 +4,10 @@
|
|||
"description": "Face Recognition plugin for Shinobi that uses C++ functions for detection. Based on @justadudewhohacks' face-api.js.",
|
||||
"main": "shinobi-face.js",
|
||||
"dependencies": {
|
||||
"@tensorflow/tfjs-converter": "^1.7.4",
|
||||
"@tensorflow/tfjs-core": "^1.7.4",
|
||||
"@tensorflow/tfjs-layers": "^1.7.4",
|
||||
"@tensorflow/tfjs-node": "^1.7.4",
|
||||
"socket.io-client": "^1.7.4",
|
||||
"express": "^4.16.2",
|
||||
"moment": "^2.19.2",
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
#!/bin/bash
|
||||
DIR=`dirname $0`
|
||||
echo "Installing coral dependencies..."
|
||||
echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list
|
||||
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install libedgetpu1-max
|
||||
sudo apt-get install libatlas-base-dev
|
||||
echo "Coral dependencies installed."
|
||||
echo "Getting coral object detection models..."
|
||||
mkdir -p models
|
||||
wget "https://github.com/google-coral/edgetpu/raw/master/test_data/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite"
|
||||
wget "https://cdn.shinobi.video/binaries/tensorflow/coral/models-2021-01-26/ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite"
|
||||
mv ssd_mobilenet_v2_coco_quant_postprocess_edgetpu.tflite models/
|
||||
wget "https://dl.google.com/coral/canned_models/coco_labels.txt"
|
||||
wget "https://cdn.shinobi.video/binaries/tensorflow/coral/models-2021-01-26/plugins_tensorflow-coral_models_coco_labels.txt"
|
||||
mv plugins_tensorflow-coral_models_coco_labels.txt coco_labels.txt
|
||||
mv coco_labels.txt models/
|
||||
echo "Models downloaded."
|
||||
|
||||
|
||||
npm install yarn -g --unsafe-perm --force
|
||||
npm install --unsafe-perm
|
||||
if [ ! -e "./conf.json" ]; then
|
||||
|
|
|
|||
|
|
@ -1,32 +1,10 @@
|
|||
# TensorFlowCoral.js
|
||||
|
||||
**Ubuntu and CentOS only**
|
||||
Follow the Official Coral PCI install instructions carefully. https://coral.ai/docs/m2/get-started/#2-install-the-pcie-driver-and-edge-tpu-runtime
|
||||
|
||||
Go to the Shinobi directory. **/home/Shinobi** is the default directory.
|
||||
# Install
|
||||
|
||||
```
|
||||
cd /home/Shinobi/plugins/tensorflow
|
||||
```
|
||||
|
||||
Install TensorFlows python version first:
|
||||
https://www.tensorflow.org/lite/guide/python
|
||||
Make sure that you are downloading the correct file for your system architecture and python version.
|
||||
|
||||
Install other python dependencies
|
||||
```
|
||||
pip install pillow
|
||||
pip install numpy
|
||||
```
|
||||
|
||||
Copy the config file.
|
||||
|
||||
```
|
||||
sh INSTALL.sh
|
||||
```
|
||||
|
||||
IF YOU DON'T HAVE INSTALLED CORAL DEPENDENCIES BEFORE, YOU NEED TO PLUG OUT AND THEN PLUG IN YOUR CORAL USB ACCELERATOR BEFORE USING THIS PLUGIN!
|
||||
|
||||
Start the plugin.
|
||||
Run INSTALL.sh
|
||||
|
||||
```
|
||||
pm2 start shinobi-tensorflow-coral.js
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
var fs = require('fs');
|
||||
var config = require('./conf.json')
|
||||
var dotenv = require('dotenv').config()
|
||||
const currentDirectory = process && process.cwd ? process.cwd() + '/' : __dirname + '/'
|
||||
var s
|
||||
const {
|
||||
workerData
|
||||
|
|
@ -49,7 +50,7 @@ var child = null
|
|||
function respawn() {
|
||||
|
||||
console.log("respawned python",(new Date()))
|
||||
const theChild = spawn('python3', ['-u', 'detect_image.py']);
|
||||
const theChild = spawn('python3', ['-u', currentDirectory + 'detect_image.py']);
|
||||
|
||||
var lastStatusLog = new Date();
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -943,7 +943,7 @@ if (typeof jQuery === 'undefined') {
|
|||
if (this.options.remote) {
|
||||
this.$element
|
||||
.find('.modal-content')
|
||||
.load(this.options.remote, $.proxy(function () {
|
||||
.on('load',this.options.remote, $.proxy(function () {
|
||||
this.$element.trigger('loaded.bs.modal')
|
||||
}, this))
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -6,7 +6,7 @@ $.apM.md.change($.ccio.form.details).first().change();
|
|||
$.apM.f.submit(function(e){
|
||||
e.preventDefault();e.e=$(this),e.s=e.e.serializeObject();
|
||||
e.er=[];
|
||||
if(!e.s.ip||e.s.ip.length<7){e.er.push('Enter atleast one IP')}
|
||||
if(!e.s.ip||e.s.ip.length<7){e.er.push('Enter at least one IP')}
|
||||
if(e.er.length>0){$.apM.e.find('.msg').html(e.er.join('<br>'));return;}
|
||||
$.each(e.s,function(n,v){e.s[n]=v.trim()})
|
||||
// e.s = {
|
||||
|
|
|
|||
|
|
@ -420,7 +420,7 @@ $(document).ready(function(e){
|
|||
}
|
||||
break;
|
||||
case'trigger-event':
|
||||
$.getJSON(getApiPrefix() + '/motion/'+e.ke+'/'+e.mid+'/?data={"plug":"camera1","name":"stairs","reason":"motion","confidence":100}',function(d){
|
||||
$.getJSON(getApiPrefix() + '/motion/'+e.ke+'/'+e.mid+'/?data={"plug":"manual_trigger","name":"Manual Trigger","reason":"Manual","confidence":100}',function(d){
|
||||
$.ccio.log(d)
|
||||
})
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -336,8 +336,8 @@ $.ccio.init=function(x,d,user,k){
|
|||
case'drawMatrices':
|
||||
d.height=d.stream.height()
|
||||
d.width=d.stream.width()
|
||||
if(!d.details.imgWidth && d.monitorDetails.detector_scale_x===''){d.monitorDetails.detector_scale_x=320}
|
||||
if(!d.details.imgHeight && d.monitorDetails.detector_scale_y===''){d.monitorDetails.detector_scale_y=240}
|
||||
if(!d.details.imgWidth && d.monitorDetails.detector_scale_x===''){d.monitorDetails.detector_scale_x=640}
|
||||
if(!d.details.imgHeight && d.monitorDetails.detector_scale_y===''){d.monitorDetails.detector_scale_y=480}
|
||||
|
||||
d.widthRatio=d.width/d.details.imgWidth || d.monitorDetails.detector_scale_x
|
||||
d.heightRatio=d.height/d.details.imgHeight || d.monitorDetails.detector_scale_y
|
||||
|
|
@ -426,9 +426,9 @@ $.ccio.init=function(x,d,user,k){
|
|||
k.run=function(){
|
||||
k.e.attr('src',$.ccio.init('location',user)+user.auth_token+'/jpeg/'+d.ke+'/'+d.mid+'/s.jpg?time='+(new Date()).getTime())
|
||||
}
|
||||
k.e.load(function(){
|
||||
k.e.on('load',function(){
|
||||
$.ccio.mon[d.ke+d.mid+user.auth_token].jpegInterval=setTimeout(k.run,1000/k.jpegInterval);
|
||||
}).error(function(){
|
||||
}).on('error',function(){
|
||||
$.ccio.mon[d.ke+d.mid+user.auth_token].jpegInterval=setTimeout(k.run,1000/k.jpegInterval);
|
||||
})
|
||||
k.run()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
$(document).ready(function(e){
|
||||
var selectedApiKey = `${$user.auth_token}`
|
||||
var getUrlPieces = function(url){
|
||||
var el = document.createElement('a');
|
||||
el.href = url
|
||||
|
|
@ -13,41 +14,70 @@ $(document).ready(function(e){
|
|||
// el.search // ?filter=a
|
||||
}
|
||||
//multi monitor manager
|
||||
$.multimon={e:$('#multi_mon')};
|
||||
$.multimon.table=$.multimon.e.find('.tableData tbody');
|
||||
$.multimon.f=$.multimon.e.find('form');
|
||||
$.multimon.f.on('change','#multimon_select_all',function(e){
|
||||
e.e=$(this);
|
||||
e.p=e.e.prop('checked')
|
||||
e.a=$.multimon.f.find('input[type=checkbox][name]')
|
||||
if(e.p===true){
|
||||
e.a.prop('checked',true)
|
||||
var theWindow = $('#multi_mon')
|
||||
var apiKeySelector = $('#multi_mon_api_key_selector')
|
||||
var theTable = theWindow.find('.tableData tbody');
|
||||
var theForm = theWindow.find('form');
|
||||
var drawApiKeyList = function(){
|
||||
$.get($.ccio.init('location',$user)+$user.auth_token+'/api/'+$user.ke+'/list',function(d){
|
||||
var html = ''
|
||||
$.each(d.keys || [],function(n,key){
|
||||
html += `<option value="${key.code}">${key.code}</option>`
|
||||
})
|
||||
apiKeySelector.find('optgroup').html(html)
|
||||
})
|
||||
}
|
||||
var drawTable = function(){
|
||||
var tmp=''
|
||||
$.each($.ccio.mon,function(n,v){
|
||||
var streamURL = $.ccio.init('streamURL',v).replace($user.auth_token,selectedApiKey)
|
||||
if(streamURL!=='Websocket'&&v.mode!==('idle'&&'stop')){
|
||||
streamURL='<a target="_blank" href="'+streamURL+'">'+streamURL+'</a>'
|
||||
}
|
||||
var img = $('#left_menu [mid="'+v.mid+'"] [monitor="watch"]').attr('src')
|
||||
tmp+='<tr mid="'+v.mid+'" ke="'+v.ke+'" auth="'+selectedApiKey+'">'
|
||||
tmp+='<td><div class="checkbox"><input id="multimonCheck_'+v.ke+v.mid+selectedApiKey+'" type="checkbox" name="'+v.ke+v.mid+selectedApiKey+'" value="1"><label for="multimonCheck_'+v.ke+v.mid+selectedApiKey+'"></label></div></td>'
|
||||
tmp+='<td><a monitor="watch"><img class="small-square-img" src="'+img+'"></a></td><td>'+v.name+'<br><small>'+v.mid+'</small></td><td class="monitor_status">'+v.status+'</td><td><small>'+streamURL+'</small></td>'
|
||||
//buttons
|
||||
tmp+='<td class="text-right"><a title="'+lang.Pop+'" monitor="pop" class="btn btn-primary"><i class="fa fa-external-link"></i></a> <a title="'+lang.Calendar+'" monitor="calendar" class="btn btn-default"><i class="fa fa-calendar"></i></a> <a title="'+lang['Power Viewer']+'" class="btn btn-default" monitor="powerview"><i class="fa fa-map-marker"></i></a> <a title="'+lang['Time-lapse']+'" class="btn btn-default" monitor="timelapse"><i class="fa fa-angle-double-right"></i></a> <a title="'+lang['Videos List']+'" monitor="videos_table" class="btn btn-default"><i class="fa fa-film"></i></a> <a title="'+lang['Monitor Settings']+'" class="btn btn-default" monitor="edit"><i class="fa fa-wrench"></i></a></td>'
|
||||
tmp+='</tr>'
|
||||
})
|
||||
theTable.html(tmp)
|
||||
}
|
||||
var getSelectedMonitors = function(unclean){
|
||||
var arr = [];
|
||||
if(unclean === true){
|
||||
var monitors = $.ccio.mon
|
||||
}else{
|
||||
e.a.prop('checked',false)
|
||||
var monitors = $.ccio.init('cleanMons','object')
|
||||
}
|
||||
$.each(theForm.serializeObject(),function(n,v){
|
||||
console.log(monitors,n)
|
||||
arr.push(monitors[n])
|
||||
})
|
||||
return arr;
|
||||
}
|
||||
theForm.on('change','#multimon_select_all',function(e){
|
||||
var el = $(this);
|
||||
var isChecked = el.prop('checked')
|
||||
var nameField = theForm.find('input[type=checkbox][name]')
|
||||
if(isChecked === true){
|
||||
nameField.prop('checked',true)
|
||||
}else{
|
||||
nameField.prop('checked',false)
|
||||
}
|
||||
})
|
||||
$.multimon.e.find('.import_config').click(function(){
|
||||
var e={};e.e=$(this);e.mid=e.e.parents('[mid]').attr('mid');
|
||||
$.confirm.e.modal('show');
|
||||
$.confirm.title.text(lang['Import Monitor Configuration'])
|
||||
e.html=lang.ImportMultiMonitorConfigurationText+'<div style="margin-top:15px"><div class="form-group"><textarea placeholder="'+lang['Paste JSON here.']+'" class="form-control"></textarea></div><label class="upload_file btn btn-primary btn-block">'+lang['Upload File']+'<input class="upload" type=file name="files[]"></label></div>';
|
||||
$.confirm.body.html(e.html)
|
||||
$.confirm.e.find('.upload').change(function(e){
|
||||
var files = e.target.files; // FileList object
|
||||
f = files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(ee) {
|
||||
$.confirm.e.find('textarea').val(ee.target.result);
|
||||
}
|
||||
reader.readAsText(f);
|
||||
});
|
||||
$.confirm.click({title:lang['Import'],class:'btn-primary'},function(){
|
||||
// setTimeout(function(){
|
||||
// $.confirm.e.modal('show');
|
||||
// },1000)
|
||||
// $.confirm.title.text(lang['Are you sure?'])
|
||||
// $.confirm.body.html(lang.ImportMultiMonitorConfigurationText)
|
||||
// $.confirm.click({title:'Save Set',class:'btn-danger'},function(){
|
||||
theWindow.find('.import_config').click(function(){
|
||||
var el = $(this);
|
||||
var html = lang.ImportMultiMonitorConfigurationText+'<div style="margin-top:15px"><div class="form-group"><textarea placeholder="'+lang['Paste JSON here.']+'" class="form-control"></textarea></div><label class="upload_file btn btn-primary btn-block">'+lang['Upload File']+'<input class="upload" type=file name="files[]"></label></div>';
|
||||
$.confirm.create({
|
||||
title: lang['Import Monitor Configuration'],
|
||||
body: html,
|
||||
clickOptions: {
|
||||
title: lang['Import'],
|
||||
class: 'btn-primary'
|
||||
},
|
||||
clickCallback: function(){
|
||||
try{
|
||||
var postMonitor = function(v){
|
||||
$.post($.ccio.init('location',$user)+$user.auth_token+'/configureMonitor/'+$user.ke+'/'+v.mid,{data:JSON.stringify(v,null,3)},function(d){
|
||||
|
|
@ -149,31 +179,28 @@ $.multimon.e.find('.import_config').click(function(){
|
|||
$.ccio.init('note',{title:lang['Invalid JSON'],text:lang.InvalidJSONText,type:'error'})
|
||||
}
|
||||
}
|
||||
// });
|
||||
}
|
||||
})
|
||||
$.confirm.e.find('.upload').change(function(e){
|
||||
var files = e.target.files; // FileList object
|
||||
f = files[0];
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(ee) {
|
||||
$.confirm.e.find('textarea').val(ee.target.result);
|
||||
}
|
||||
reader.readAsText(f);
|
||||
});
|
||||
})
|
||||
$.multimon.getSelectedMonitors = function(unclean){
|
||||
var arr=[];
|
||||
if(unclean === true){
|
||||
var monitors = $.ccio.mon
|
||||
}else{
|
||||
var monitors = $.ccio.init('cleanMons','object')
|
||||
}
|
||||
$.each($.multimon.f.serializeObject(),function(n,v){
|
||||
arr.push(monitors[n])
|
||||
})
|
||||
return arr;
|
||||
}
|
||||
$.multimon.e.find('.delete').click(function(){
|
||||
var arr=$.multimon.getSelectedMonitors(true);
|
||||
theWindow.find('.delete').click(function(){
|
||||
var arr = getSelectedMonitors(true);
|
||||
if(arr.length===0){
|
||||
$.ccio.init('note',{title:lang['No Monitors Selected'],text:lang['Select atleast one monitor to delete'],type:'error'});
|
||||
return
|
||||
}
|
||||
$.confirm.e.modal('show');
|
||||
$.confirm.title.text(lang['Delete']+' '+lang['Monitors'])
|
||||
e.html='<p>'+lang.DeleteMonitorsText+'</p>';
|
||||
$.confirm.body.html(e.html)
|
||||
var html = '<p>'+lang.DeleteMonitorsText+'</p>';
|
||||
$.confirm.body.html(html)
|
||||
$.confirm.click([
|
||||
{
|
||||
title:lang['Delete']+' '+lang['Monitors'],
|
||||
|
|
@ -199,45 +226,28 @@ $.multimon.e.find('.delete').click(function(){
|
|||
}
|
||||
]);
|
||||
})
|
||||
//$.multimon.e.find('.edit_all').click(function(){
|
||||
// var arr=$.multimon.getSelectedMonitors();
|
||||
// var arrObject={}
|
||||
// if(arr.length===0){
|
||||
// $.ccio.init('note',{title:lang['No Monitors Selected'],text:lang['Select atleast one monitor to delete'],type:'error'});
|
||||
// return
|
||||
// }
|
||||
// $.multimonedit.selectedList = arr;
|
||||
// $.multimonedit.e.modal('show')
|
||||
//})
|
||||
$.multimon.e.find('.save_config').click(function(){
|
||||
var e={};e.e=$(this);
|
||||
var arr=$.multimon.getSelectedMonitors();
|
||||
theWindow.find('.save_config').click(function(){
|
||||
var el = $(this);
|
||||
var arr = getSelectedMonitors();
|
||||
if(arr.length===0){
|
||||
$.ccio.init('note',{title:lang['No Monitors Selected'],text:lang['Select atleast one monitor to delete'],type:'error'});
|
||||
return
|
||||
}
|
||||
e.dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(arr));
|
||||
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(arr));
|
||||
$('#temp').html('<a></a>')
|
||||
.find('a')
|
||||
.attr('href',e.dataStr)
|
||||
.attr('href',dataStr)
|
||||
.attr('download','Shinobi_Monitors_'+(new Date())+'.json')
|
||||
[0].click()
|
||||
})
|
||||
$.multimon.e.on('shown.bs.modal',function() {
|
||||
var tmp=''
|
||||
$.each($.ccio.mon,function(n,v){
|
||||
var streamURL = $.ccio.init('streamURL',v)
|
||||
if(streamURL!=='Websocket'&&v.mode!==('idle'&&'stop')){
|
||||
streamURL='<a target="_blank" href="'+streamURL+'">'+streamURL+'</a>'
|
||||
}
|
||||
var img = $('#left_menu [mid="'+v.mid+'"][auth="'+v.user.auth_token+'"] [monitor="watch"]').attr('src')
|
||||
tmp+='<tr mid="'+v.mid+'" ke="'+v.ke+'" auth="'+v.user.auth_token+'">'
|
||||
tmp+='<td><div class="checkbox"><input id="multimonCheck_'+v.ke+v.mid+v.user.auth_token+'" type="checkbox" name="'+v.ke+v.mid+v.user.auth_token+'" value="1"><label for="multimonCheck_'+v.ke+v.mid+v.user.auth_token+'"></label></div></td>'
|
||||
tmp+='<td><a monitor="watch"><img class="small-square-img" src="'+img+'"></a></td><td>'+v.name+'<br><small>'+v.mid+'</small></td><td class="monitor_status">'+v.status+'</td><td>'+streamURL+'</td>'
|
||||
//buttons
|
||||
tmp+='<td class="text-right"><a title="'+lang.Pop+'" monitor="pop" class="btn btn-primary"><i class="fa fa-external-link"></i></a> <a title="'+lang.Calendar+'" monitor="calendar" class="btn btn-default"><i class="fa fa-calendar"></i></a> <a title="'+lang['Power Viewer']+'" class="btn btn-default" monitor="powerview"><i class="fa fa-map-marker"></i></a> <a title="'+lang['Time-lapse']+'" class="btn btn-default" monitor="timelapse"><i class="fa fa-angle-double-right"></i></a> <a title="'+lang['Videos List']+'" monitor="videos_table" class="btn btn-default"><i class="fa fa-film"></i></a> <a title="'+lang['Monitor Settings']+'" class="btn btn-default" monitor="edit"><i class="fa fa-wrench"></i></a></td>'
|
||||
tmp+='</tr>'
|
||||
})
|
||||
$.multimon.table.html(tmp)
|
||||
theWindow.on('shown.bs.modal',function() {
|
||||
drawTable()
|
||||
drawApiKeyList()
|
||||
})
|
||||
apiKeySelector.change(function(){
|
||||
var value = $(this).val()
|
||||
console.log(value)
|
||||
selectedApiKey = `${value}`
|
||||
drawTable()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,299 @@
|
|||
$(document).ready(function(){
|
||||
var apiPrefix = getAdminApiPrefix()
|
||||
var theWindow = $('#subAccountManager');
|
||||
var accountTable = $('#subAccountsList tbody');
|
||||
var theWindowForm = $('#monSectionAccountInformation');
|
||||
var permissionsSection = $('#monSectionAccountPrivileges');
|
||||
var permissionsMonitorSection = $('#sub_accounts_permissions');
|
||||
var submitButtons = theWindow.find('.submit-form')
|
||||
var loadedSubAccounts = {}
|
||||
var clearTable = function(){
|
||||
accountTable.empty()
|
||||
loadedSubAccounts = {}
|
||||
}
|
||||
var getSubAccounts = function(){
|
||||
$.get(`${apiPrefix}accounts/${$user.ke}`,function(data){
|
||||
clearTable()
|
||||
$.each(data.accounts,function(n,account){
|
||||
loadedSubAccounts[account.uid] = account;
|
||||
drawSubAccountRow(account)
|
||||
})
|
||||
})
|
||||
}
|
||||
var deleteSubAccount = function(email,uid){
|
||||
$.confirm.create({
|
||||
title: lang.deleteSubAccount,
|
||||
body: lang.deleteSubAccountText + '\n' + email,
|
||||
clickOptions: {
|
||||
class: 'btn-danger',
|
||||
title: lang.Delete,
|
||||
},
|
||||
clickCallback: function(){
|
||||
$.post(apiPrefix+'accounts/'+$user.ke+'/delete',{
|
||||
uid: uid,
|
||||
mail: email
|
||||
},function(data){
|
||||
var notifyTitle = lang.accountDeleted
|
||||
var notifyText = lang.accountDeletedText + '\n' + email
|
||||
var notifyColor = 'info'
|
||||
if(data.ok){
|
||||
loadedSubAccounts[uid] = null;
|
||||
accountTable.find('tr[uid="' + uid + '"]').remove()
|
||||
}else{
|
||||
notifyTitle = lang.accountActionFailed
|
||||
notifyText = lang.contactAdmin
|
||||
notifyColor = 'warning'
|
||||
}
|
||||
new PNotify({
|
||||
title : notifyTitle,
|
||||
text : notifyText,
|
||||
type : notifyColor
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
var addSubAccount = function(newAccount,callback){
|
||||
$.post(apiPrefix+'accounts/'+$user.ke+'/register',{
|
||||
data: JSON.stringify(newAccount)
|
||||
},function(data){
|
||||
var notifyTitle
|
||||
var notifyText
|
||||
var notifyColor
|
||||
if(!data.ok && data.msg){
|
||||
notifyTitle = lang.accountActionFailed
|
||||
notifyText = data.msg
|
||||
notifyColor = 'warning'
|
||||
}
|
||||
if(data.user){
|
||||
notifyTitle = lang.accountAdded
|
||||
notifyText = lang.accountAddedText + '\n' + data.user.mail
|
||||
notifyColor = 'success'
|
||||
if(data.user){
|
||||
var account = data.user
|
||||
loadedSubAccounts[account.uid] = account;
|
||||
drawSubAccountRow(account)
|
||||
theWindowForm.find('[name="uid"]').val(account.uid)
|
||||
setSubmitButtonState(lang['Save Changes'],'check')
|
||||
}else{
|
||||
notifyTitle = lang.accountActionFailed
|
||||
notifyText = lang.contactAdmin
|
||||
notifyColor = 'warning'
|
||||
}
|
||||
}
|
||||
new PNotify({
|
||||
title : notifyTitle,
|
||||
text : notifyText,
|
||||
type : notifyColor
|
||||
})
|
||||
callback(data)
|
||||
});
|
||||
}
|
||||
var editSubaccount = function(uid,form,callback){
|
||||
var account = loadedSubAccounts[uid]
|
||||
$.post(apiPrefix+'accounts/'+$user.ke+'/edit',{
|
||||
uid: uid,
|
||||
mail: form.mail,
|
||||
data: form
|
||||
},function(data){
|
||||
if(data.ok){
|
||||
$.each(form,function(n,v){
|
||||
account[n] = v
|
||||
});
|
||||
accountTable.find(`[uid="${account.uid}"] .mail`).text(form.mail)
|
||||
new PNotify({
|
||||
title : 'Account Edited',
|
||||
text : '<b>' + account.mail + '</b> has been updated.',
|
||||
type : 'success'
|
||||
})
|
||||
}else{
|
||||
new PNotify({
|
||||
title : 'Failed to Add Account',
|
||||
text : data.msg,
|
||||
type : 'error'
|
||||
})
|
||||
}
|
||||
callback(data)
|
||||
})
|
||||
}
|
||||
var drawSubAccountRow = function(account){
|
||||
var html = `<tr uid="${account.uid}">
|
||||
<td>
|
||||
<span class="badge btn-primary mail">${account.mail}</span>
|
||||
</td>
|
||||
<td>
|
||||
<code class="uid">${account.uid}</code>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a class="permission btn btn-sm btn-primary"><i class="fa fa-lock"></i></a>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a class="delete btn btn-sm btn-danger"><i class="fa fa-trash-o"></i></a>
|
||||
</td>
|
||||
</tr>`;
|
||||
accountTable.prepend(html)
|
||||
}
|
||||
var permissionTypeNames = [
|
||||
{
|
||||
name: 'monitors',
|
||||
label: lang['Can View Monitor'],
|
||||
},
|
||||
{
|
||||
name: 'monitor_edit',
|
||||
label: lang['Can Edit Monitor'],
|
||||
},
|
||||
{
|
||||
name: 'video_view',
|
||||
label: lang['Can View Videos and Events'],
|
||||
},
|
||||
{
|
||||
name: 'video_delete',
|
||||
label: lang['Can Delete Videos and Events'],
|
||||
},
|
||||
];
|
||||
var drawSelectableForPermissionForm = function(){
|
||||
var html = ''
|
||||
$.each($.ccio.mon,function(n,monitor){
|
||||
html += `<div class="form-group permission-view">`
|
||||
html += `<div><label>${monitor.name} (${monitor.mid})</label></div>`
|
||||
html += `<div><select class="form-control" multiple monitor="${monitor.mid}">`
|
||||
$.each(permissionTypeNames,function(n,permission){
|
||||
html += `<option value="${permission.name}">${permission.label}</option>`
|
||||
})
|
||||
html += `</select></div>`
|
||||
html += `</div>`
|
||||
})
|
||||
permissionsMonitorSection.html(html)
|
||||
}
|
||||
var setPermissionSelectionsToFields = function(uid){
|
||||
var account = loadedSubAccounts[uid]
|
||||
var details = $.parseJSON(account.details)
|
||||
// load values to Account Information : email, password, etc.
|
||||
$.each(account,function(n,v){
|
||||
theWindowForm.find('[name="'+n+'"]').val(v)
|
||||
})
|
||||
// load base privileges
|
||||
permissionsSection.find('[detail]').each(function(n,v){
|
||||
var el = $(v)
|
||||
var key = el.attr('detail')
|
||||
var defaultValue = el.attr('data-default')
|
||||
el.val(details[key] || defaultValue)
|
||||
})
|
||||
permissionsSection.find('[detail="allmonitors"]').change()
|
||||
// load montior specific privileges
|
||||
$.each($.ccio.mon,function(m,monitor){
|
||||
$.each(permissionTypeNames,function(m,permission){
|
||||
if((details[permission.name] || []).indexOf(monitor.mid) > -1){
|
||||
permissionsSection.find(`[monitor="${monitor.mid}"] option[value="${permission.name}"]`).attr("selected", "selected")
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
var openSubAccountEditor = function(uid){
|
||||
var account = loadedSubAccounts[uid]
|
||||
drawSelectableForPermissionForm()
|
||||
setPermissionSelectionsToFields(uid)
|
||||
theWindowForm.find('[name="pass"],[name="password_again"]').val('')
|
||||
permissionsSection.show()
|
||||
}
|
||||
var writePermissionsFromFieldsToString = function(){
|
||||
var foundSelected = {}
|
||||
var detailsElement = theWindowForm.find('[name="details"]')
|
||||
var details = JSON.parse(detailsElement.val())
|
||||
details = details ? details : {sub: 1, allmonitors: "1"}
|
||||
// base privileges
|
||||
permissionsSection.find('[detail]').each(function(n,v){
|
||||
var el = $(v)
|
||||
details[el.attr('detail')] = el.val()
|
||||
})
|
||||
// monitor specific privileges
|
||||
permissionsSection.find('.permission-view select').each(function(n,v){
|
||||
var el = $(v)
|
||||
var monitorId = el.attr('monitor')
|
||||
var value = el.val()
|
||||
$.each(value,function(n,permissionNameSelected){
|
||||
if(!foundSelected[permissionNameSelected])foundSelected[permissionNameSelected] = []
|
||||
foundSelected[permissionNameSelected].push(monitorId)
|
||||
})
|
||||
})
|
||||
details = Object.assign(details,foundSelected)
|
||||
detailsElement.val(JSON.stringify(details))
|
||||
}
|
||||
var getCompleteForm = function(){
|
||||
writePermissionsFromFieldsToString()
|
||||
return theWindowForm.serializeObject()
|
||||
}
|
||||
var setSubmitButtonState = function(text,icon){
|
||||
submitButtons.html(`<i class="fa fa-${icon}"></i> ${text}`)
|
||||
}
|
||||
//add new
|
||||
submitButtons.click(function(){
|
||||
theWindowForm.submit()
|
||||
})
|
||||
theWindowForm.submit(function(e){
|
||||
e.preventDefault();
|
||||
var formValues = getCompleteForm()
|
||||
var uid = formValues.uid
|
||||
console.log(formValues)
|
||||
if(formValues.uid){
|
||||
console.log('edit')
|
||||
editSubaccount(uid,formValues,function(data){
|
||||
console.log(data)
|
||||
})
|
||||
}else{
|
||||
addSubAccount(formValues,function(data){
|
||||
console.log(data)
|
||||
})
|
||||
}
|
||||
return false;
|
||||
});
|
||||
//sub simple lister
|
||||
theWindow.on('click','.delete',function(e){
|
||||
var el = $(this).parents('tr')
|
||||
var subAccountEmail = el.find('.mail').text()
|
||||
var subAccountUid = el.attr('uid')
|
||||
deleteSubAccount(subAccountEmail,subAccountUid)
|
||||
})
|
||||
theWindow.on('click','.permission',function(e){
|
||||
var el = $(this).parents('tr')
|
||||
var uid = el.attr('uid')
|
||||
openSubAccountEditor(uid)
|
||||
setSubmitButtonState(lang['Save Changes'],'check')
|
||||
})
|
||||
theWindow.on('click','.reset-form',function(e){
|
||||
permissionsSection.find('[detail]').each(function(n,v){
|
||||
var el = $(v)
|
||||
var key = el.attr('detail')
|
||||
var defaultValue = el.attr('data-default')
|
||||
el.val(defaultValue)
|
||||
})
|
||||
drawSelectableForPermissionForm()
|
||||
setSubmitButtonState(lang['Add New'],'plus')
|
||||
theWindowForm.find('[name="pass"],[name="password_again"]').val('')
|
||||
})
|
||||
|
||||
permissionsSection.on('click','[check]',function(e){
|
||||
$(this).parents('.form-group-group').find('select').val($(this).attr('check')).first().change()
|
||||
})
|
||||
// permissionsSection.on('change','[monitor]',function(e){
|
||||
// writePermissionsFromFieldsToString()
|
||||
// });
|
||||
theWindow.on('shown.bs.modal',function() {
|
||||
getSubAccounts()
|
||||
if(theWindowForm.find('[name="uid"]').val() === '')drawSelectableForPermissionForm()
|
||||
})
|
||||
theWindow.on('hidden.bs.modal',function() {
|
||||
clearTable()
|
||||
})
|
||||
permissionsSection.on('change','[detail="allmonitors"]',function(e){
|
||||
var value = $(this).val()
|
||||
var el = $('.permission-view')
|
||||
if(value === '1'){
|
||||
el.hide();
|
||||
}else{
|
||||
el.show()
|
||||
}
|
||||
})
|
||||
// TEST
|
||||
window.getCompleteForm = getCompleteForm
|
||||
})
|
||||
|
|
@ -82,7 +82,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<table class="table table-striped" id="api_list"></table>
|
||||
<table class="table table-striped" id="api_list" style="color: #fff;"></table>
|
||||
</div>
|
||||
<input type="hidden" name="details">
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,13 @@
|
|||
</form>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default pull-left" data-dismiss="modal"><%-lang.Close%></button>
|
||||
<div>
|
||||
<div style="display:inline-block;margin-right:5px;font-family: monospace">
|
||||
<div><select class="form-control btn-default" id="multi_mon_api_key_selector">
|
||||
<option value="<%- $user.auth_token %>" selected><%-lang['Session Key']%></option>
|
||||
<optgroup label="<%- lang['API Keys'] %>"></optgroup>
|
||||
</select></div>
|
||||
</div>
|
||||
<div style="display:inline-block">
|
||||
<a class="btn btn-danger delete"><%-lang['Delete']%></a>
|
||||
<a class="btn btn-primary save_config"><%-lang['Export']%></a>
|
||||
<a class="btn btn-default import_config"><%-lang['Import']%></a>
|
||||
|
|
|
|||
|
|
@ -68,9 +68,11 @@
|
|||
if(monitorSettings.attribute){
|
||||
attributes.push(monitorSettings.attribute)
|
||||
}
|
||||
if(monitorSettings.id){
|
||||
attributes.push(`id="${monitorSettings.id}"`)
|
||||
if(!monitorSettings.id){
|
||||
var userSettingsId = monitorSettings.name.replace(/[^a-zA-Z ]/g, '').replace(/[^a-zA-Z ]/g, '').replace(/ /g, '')
|
||||
monitorSettings.id = userSettingsId
|
||||
}
|
||||
attributes.push(`id="${monitorSettings.id}"`)
|
||||
if(monitorSettings.color){
|
||||
sectionClass.push(monitorSettings.color)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,290 @@
|
|||
<!--Sub Account Manager Window-->
|
||||
<div class="modal fade" id="subAccountManager" tabindex="-1" role="dialog" aria-labelledby="subAccountManagerLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title" id="subAccountManagerLabel"><i class="fa fa-group"></i> <%-lang.subAccountManager%></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<%
|
||||
var buildOptions = function(field,possiblities){
|
||||
if(!field)console.error('field',field)
|
||||
var fieldElement = ''
|
||||
possiblities.forEach(function(option){
|
||||
if(option.optgroup){
|
||||
fieldElement += '<optgroup label="' + option.name + '">'
|
||||
fieldElement += buildOptions(field,option.optgroup)
|
||||
fieldElement += '</optgroup>'
|
||||
}else{
|
||||
var selected = ''
|
||||
if(option.value === field.default){
|
||||
selected = 'selected'
|
||||
}
|
||||
fieldElement += '<option value="' + option.value + '" ' + selected + '>' + option.name + '</option>'
|
||||
}
|
||||
})
|
||||
return fieldElement
|
||||
}
|
||||
var drawBlock = function(monitorSettings){
|
||||
if(monitorSettings.evaluation && !eval(monitorSettings.evaluation)){
|
||||
return
|
||||
}
|
||||
var attributes = []
|
||||
var styles = []
|
||||
var sectionClass = []
|
||||
var headerTitle = monitorSettings.headerTitle || lang[monitorSettings.name] || monitorSettings.name
|
||||
if(monitorSettings.hidden === true){
|
||||
styles.push('display:none')
|
||||
}
|
||||
if(monitorSettings.style){
|
||||
styles.push(monitorSettings.style)
|
||||
}
|
||||
if(monitorSettings.isSection === true){
|
||||
attributes.push('section')
|
||||
}
|
||||
if(monitorSettings.attribute){
|
||||
attributes.push(monitorSettings.attribute)
|
||||
}
|
||||
if(!monitorSettings.id){
|
||||
var userSettingsId = monitorSettings.name.replace(/[^a-zA-Z ]/g, '').replace(/[^a-zA-Z ]/g, '').replace(/ /g, '')
|
||||
monitorSettings.id = userSettingsId
|
||||
}
|
||||
attributes.push(`id="${monitorSettings.id}"`)
|
||||
if(monitorSettings.color){
|
||||
sectionClass.push(monitorSettings.color)
|
||||
}
|
||||
if(monitorSettings['section-class']){
|
||||
sectionClass.push(monitorSettings['section-class'])
|
||||
}
|
||||
if(monitorSettings.isAdvanced){ %>
|
||||
<div class="h_us_input h_us_advanced" style="display:none">
|
||||
<% }
|
||||
if(monitorSettings['section-pre-pre-class']){ %>
|
||||
<div class="<%- monitorSettings['section-pre-pre-class'] %>">
|
||||
<% }
|
||||
if(monitorSettings['section-pre-class']){ %>
|
||||
<div class="<%- monitorSettings['section-pre-class'] %>">
|
||||
<% }
|
||||
%>
|
||||
<<%- monitorSettings.isForm ? 'form' : 'div' %> <%- attributes.join(' ') %> style="<%- styles.join(';') %>" class="form-group-group <%- sectionClass.join(' ') %>">
|
||||
<h4 class="monitor-section-header <%- monitorSettings.headerClass %>"><%- headerTitle %>
|
||||
<% if(monitorSettings.headerButtons){ %>
|
||||
<div class="pull-right">
|
||||
<% monitorSettings.headerButtons.forEach(function(button){ %>
|
||||
<a class="btn btn-success btn-xs <%- button.class %>">
|
||||
<% if(button.icon){ %><i class="fa fa-<%- button.icon %>"></i><% } %>
|
||||
<% if(button.text){ %><%- button.text %><% } %>
|
||||
</a>
|
||||
<% }) %>
|
||||
</div>
|
||||
<% } %>
|
||||
</h4>
|
||||
<div class="box-wrapper">
|
||||
<% if(monitorSettings['input-mapping']){ %>
|
||||
<div class="form-group-group forestgreen" style="display:none" input-mapping="<%- monitorSettings['input-mapping'] %>">
|
||||
<h4><%-lang['Input Feed']%>
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-success btn-xs add_map_row"><i class="fa fa-plus-square-o"></i></a>
|
||||
</div>
|
||||
</h4>
|
||||
<div class="choices"></div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if(monitorSettings.blockquote){ %>
|
||||
<blockquote class="<%- monitorSettings.blockquoteClass || '' %>">
|
||||
<%- monitorSettings.blockquote %>
|
||||
</blockquote>
|
||||
<% } %>
|
||||
<% if(monitorSettings.blocks){
|
||||
monitorSettings.blocks.forEach(function(settingsBlock){
|
||||
drawBlock(settingsBlock)
|
||||
})
|
||||
}
|
||||
if(monitorSettings.info){
|
||||
monitorSettings.info.forEach(function(field){
|
||||
if(field.isFormGroupGroup === true){
|
||||
drawBlock(field)
|
||||
}else{
|
||||
if(field.notForSubAccount === true){
|
||||
var notForSubAccount = '!details.sub'
|
||||
if(!field.evaluation){
|
||||
field.evaluation = notForSubAccount
|
||||
}else{
|
||||
field.evaluation += ' && ' + notForSubAccount
|
||||
}
|
||||
}
|
||||
if(field.evaluation && !eval(field.evaluation)){
|
||||
return
|
||||
}
|
||||
var hidden = ''
|
||||
if(field.hidden === true){
|
||||
hidden = 'style="display:none"'
|
||||
}
|
||||
var fieldClass = []
|
||||
var attributes = []
|
||||
if(field.name && field.name.indexOf('detail=') > -1){
|
||||
attributes.push(field.name)
|
||||
}else if(field.name){
|
||||
attributes.push("name=" + field.name)
|
||||
}
|
||||
if(field.placeholder || field.default){
|
||||
attributes.push(`placeholder="${field.placeholder || field.default}"`)
|
||||
}else if(field.example){
|
||||
attributes.push(`placeholder="Example : ${field.example}"`)
|
||||
}
|
||||
if(field.default){
|
||||
attributes.push(`data-default="${field.default}"`)
|
||||
}
|
||||
if(field.attribute){
|
||||
attributes.push(field.attribute)
|
||||
}
|
||||
if(field.selector){
|
||||
attributes.push(`selector="${field.selector}"`)
|
||||
}
|
||||
if(field.id){
|
||||
attributes.push(`id="${field.id}"`)
|
||||
}
|
||||
if(field.class){
|
||||
fieldClass.push(`${field.class}`)
|
||||
}
|
||||
var possiblities = field.possible || []
|
||||
var fieldType = field.fieldType || 'text'
|
||||
var fieldElement = ''
|
||||
var preFill = field.preFill || ''
|
||||
switch(fieldType){
|
||||
case'btn':
|
||||
baseElement = field.forForm ? 'button' : 'a'
|
||||
fieldElement = `<${baseElement} class="btn btn-block ${fieldClass.join(' ')}" ${attributes.join(' ')}>${field.btnContent}</${baseElement}>`
|
||||
break;
|
||||
case'ul':
|
||||
fieldElement = `<ul ${attributes.join(' ')} class="${fieldClass.join(' ')}" ></ul>`
|
||||
break;
|
||||
case'div':
|
||||
fieldElement = `<div ${attributes.join(' ')} class="${fieldClass.join(' ')}" ></div>`
|
||||
break;
|
||||
case'form':
|
||||
fieldElement = `<form ${attributes.join(' ')} class="${fieldClass.join(' ')}" ></form>`
|
||||
break;
|
||||
case'table':
|
||||
fieldElement = `<table ${attributes.join(' ')} class="${fieldClass.join(' ')}"><tbody></tbody></table>`
|
||||
break;
|
||||
case'number':
|
||||
if(field.numberMin){
|
||||
attributes.push(`min="${field.numberMin}"`)
|
||||
}
|
||||
if(field.numberMax){
|
||||
attributes.push(`max="${field.numberMax}"`)
|
||||
}
|
||||
fieldElement = '<div><input type="number" class="form-control" ' + attributes.join(' ') + '></div>'
|
||||
break;
|
||||
case'password':
|
||||
fieldElement = '<div><input type="password" class="form-control" ' + attributes.join(' ') + '></div>'
|
||||
break;
|
||||
case'text':
|
||||
fieldElement = `<div><input class="form-control" ${attributes.join(' ')} value="${preFill}"></div>`
|
||||
break;
|
||||
case'textarea':
|
||||
fieldElement = '<div><textarea class="form-control" ' + attributes.join(' ') + '></textarea></div>'
|
||||
break;
|
||||
case'select':
|
||||
fieldElement = '<div><select class="form-control" ' + attributes.join(' ') + '>'
|
||||
fieldElement += buildOptions(field,possiblities)
|
||||
fieldElement += '</select></div>'
|
||||
break;
|
||||
}
|
||||
if(field['form-group-class-pre-pre-layer']){ %>
|
||||
<div class="<%- field['form-group-class-pre-pre-layer'] %>">
|
||||
<% }
|
||||
if(field['form-group-class-pre-layer']){ %>
|
||||
<div class="<%- field['form-group-class-pre-layer'] %>">
|
||||
<% }
|
||||
if(fieldType === 'ul' || fieldType === 'div' || fieldType === 'btn' || fieldType === 'table' || fieldType === 'form'){ %>
|
||||
<%- fieldElement %>
|
||||
<% }else{ %>
|
||||
<div <%- hidden %> class="form-group <%- field['form-group-class'] %>">
|
||||
<label><div><span><%- field.field %>
|
||||
<% if(field.description){ %>
|
||||
<small><%- field.description %></small>
|
||||
<% } %>
|
||||
</span></div>
|
||||
<%- fieldElement %>
|
||||
</label>
|
||||
</div>
|
||||
<% }
|
||||
}
|
||||
if(field['form-group-class-pre-layer']){ %>
|
||||
</div>
|
||||
<% }
|
||||
if(field['form-group-class-pre-pre-layer']){ %>
|
||||
</div>
|
||||
<% }
|
||||
})
|
||||
}
|
||||
%>
|
||||
</div>
|
||||
</<%- monitorSettings.isForm ? 'form' : 'div' %>>
|
||||
<%
|
||||
if(monitorSettings['section-pre-class']){ %>
|
||||
</div>
|
||||
<% }
|
||||
if(monitorSettings['section-pre-pre-class']){ %>
|
||||
</div>
|
||||
<% }
|
||||
if(monitorSettings.isAdvanced){ %>
|
||||
</div>
|
||||
<% }
|
||||
}
|
||||
%>
|
||||
<% Object.keys(define['Sub-Account Manager'].blocks).forEach(function(blockKey){
|
||||
var accountSettings = define['Sub-Account Manager'].blocks[blockKey]
|
||||
drawBlock(accountSettings)
|
||||
}) %>
|
||||
<!-- <div class="row">
|
||||
<div class="col-md-4">
|
||||
<table class="table table-striped" id="sub_accounts">
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<form id="sub_accounts_add_form">
|
||||
<div class="form-group-group forestgreen">
|
||||
<h4>Add<small id="msg" class="pull-right" style="color:#fff"></small></h4>
|
||||
<div class="form-group">
|
||||
<label><div><span>Email</span></div>
|
||||
<div><input class="form-control" type="email" name="mail"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span>Password</span></div>
|
||||
<div><input class="form-control" type="password" name="pass"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label><div><span>Password Again</span></div>
|
||||
<div><input class="form-control" type="password" name="password_again"></div>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="reset" class="btn btn-danger"><i class="fa fa-undo"></i> Clear</button>
|
||||
<div class="pull-right">
|
||||
<button type="submit" class="btn btn-success"><i class="fa fa-check"></i> Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default pull-left" data-dismiss="modal"><i class="fa fa-times"></i> <%-lang.Close%></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="<%-window.libURL%>libs/js/dash2.subAccountManager.js"></script>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<!--Confirmation Window-->
|
||||
<!--Sub Account Permission Editor Window-->
|
||||
<div class="modal fade" id="permissions" tabindex="-1" role="dialog" aria-labelledby="permissionsLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content">
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<li class="mdl-menu__item" data-toggle="modal" data-target="#shinobihub_viewer"><div><i class="fa fa-home"></i><div><%- lang.ShinobiHub %></div></div></li>
|
||||
<li class="mdl-menu__item" data-toggle="modal" data-target="#eventCounts"><div><i class="fa fa-eye"></i><div><%- lang['Event Counts'] %></div></div></li>
|
||||
<% if(!details.sub){ %>
|
||||
<li class="mdl-menu__item" data-toggle="modal" data-target="#subAccountManager"><div><i class="fa fa-group"></i><div><%- lang.subAccountManager %></div></div></li>
|
||||
<li class="mdl-menu__item" data-toggle="modal" data-target="#onvif_probe"><div><i class="fa fa-rss"></i><div><%- lang.ONVIF %></div></div></li>
|
||||
<li class="mdl-menu__item" data-toggle="modal" data-target="#probe"><div><i class="fa fa-search"></i><div><%- lang.FFprobe %></div></div></li>
|
||||
<li class="mdl-menu__item" data-toggle="modal" data-target="#monitorStates"><div><i class="fa fa-align-right"></i><div><%- lang['Monitor States'] %></div></div></li>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
<%
|
||||
var details = JSON.parse($user.details)
|
||||
if(!details.sub){ %>
|
||||
<script>
|
||||
window.getAdminApiPrefix = function(piece){
|
||||
return location.search === '?p2p=1' ? location.pathname + '/' : "<%=originalURL%><%=config.webPaths.adminApiPrefix%><%- $user.auth_token %>/"
|
||||
}
|
||||
</script>
|
||||
<% }
|
||||
%>
|
||||
<% include blocks/header %>
|
||||
<script>var $user = <%- JSON.stringify($user) %>;</script>
|
||||
|
|
@ -172,6 +179,7 @@
|
|||
<% include blocks/schedules.ejs %>
|
||||
<% include blocks/confirm.ejs %>
|
||||
<% include blocks/shinobiHub.ejs %>
|
||||
<% include blocks/subAccountManager.ejs %>
|
||||
<% customAutoLoad.PageBlocks.forEach(function(block){ %>
|
||||
<%- include(block) %>
|
||||
<% }) %>
|
||||
|
|
@ -231,3 +239,14 @@
|
|||
<% } %>
|
||||
|
||||
<% include blocks/onvifDeviceManager.ejs %>
|
||||
<%
|
||||
if(!details.sub){ %>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
if(`${location.pathname}/` === `<%=config.webPaths.adminApiPrefix%>`){
|
||||
$('#subAccountManager').modal('show')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<% }
|
||||
%>
|
||||
|
|
|
|||
Loading…
Reference in New Issue