Merge branch 'dev' into 'master'

The Awaited

See merge request Shinobi-Systems/Shinobi!466
node-20
Moe 2023-04-19 19:56:52 +00:00
commit 4e6e0eb1f5
30 changed files with 637 additions and 395 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ dist
generatedLanguageFiles
faces
unknownFaces
.idea/

View File

@ -81,7 +81,7 @@ if [ ! -e "/config/conf.json" ]; then
node tools/modifyConfiguration.js cpuUsageMarker=CPU subscriptionId=$SUBSCRIPTION_ID thisIsDocker=true pluginKeys="$PLUGIN_KEYS" databaseType="$DB_TYPE" db="$DATABASE_CONFIG" ssl="$SSL_CONFIG"
cp /config/conf.json conf.json
fi
sudo sed -i -e 's/change_this_to_something_very_random__just_anything_other_than_this/'"$cronKey"'/g' conf.json
sed -i -e 's/change_this_to_something_very_random__just_anything_other_than_this/'"$cronKey"'/g' conf.json
echo "============="

View File

@ -88,6 +88,7 @@ COPY . .
#RUN rm -rf /home/Shinobi/plugins
COPY ./plugins /home/Shinobi/plugins
RUN chmod -R 777 /home/Shinobi/plugins
RUN chmod 777 /home/Shinobi
RUN npm i npm@latest -g && \
npm install --unsafe-perm && \
npm install pm2 -g

View File

@ -7962,6 +7962,18 @@ module.exports = function(s,config,lang){
isFormGroupGroup: true,
'section-class': 'text-center',
"info": [
{
"fieldType": "btn-group",
"normalWidth": true,
"btns": [
{
"fieldType": "btn",
"class": `btn-success btn-sm`,
"attribute": `powerVideo-control="downloadPlaying" title="${lang['Download']}"`,
"btnContent": `<i class="fa fa-download"></i>`,
}
],
},
{
"fieldType": "btn-group",
"normalWidth": true,

View File

@ -858,6 +858,7 @@
"Country of Plates": "Country of Plates",
"Email on No Motion": "Email on \"No Motion\"",
"Discord on No Motion": "Discord on \"No Motion\"",
"No README found": "No README found",
"Timeout": "Timeout",
"Controllable": "Controllable",
"Custom Base URL": "Custom Base URL <small>Leave blank to use Host URL</small>",

View File

@ -45,7 +45,9 @@ const rawMonitorConfig = jsonData.rawMonitorConfig
// var writeToStderr = function(text){
// process.stderr.write(Buffer.from(text))
// }
var timeout = setTimeout(() => {
exitAction()
},10000)
var snapProcess = spawn(ffmpegAbsolutePath,ffmpegCommandString,{detached: true})
snapProcess.stderr.on('data',(data)=>{
writeToStderr(data.toString())
@ -54,6 +56,7 @@ snapProcess.stdout.on('data',(data)=>{
writeToStderr(data.toString())
})
snapProcess.on('close',function(data){
clearTimeout(timeout)
if(useIcon){
var iconStream = fs.createWriteStream(iconImageFile);
var fileCopy = fs.createReadStream(temporaryImageFile).pipe(iconStream)

View File

@ -47,11 +47,21 @@ module.exports = function(s,config,lang){
ok: true,
type: lang[doStart ? 'Control Triggered' : 'Control Trigger Ended']
}
const theRequest = fetchWithAuthentication(requestUrl,{
method: controlUrlMethod || controlOptions.method,
digestAuth: hasDigestAuthEnabled,
postData: controlOptions.postData || null
});
var fetchWithAuthData;
if (controlOptions.postData){
fetchWithAuthData = {
method: controlUrlMethod || controlOptions.method,
digestAuth: hasDigestAuthEnabled,
postData: controlOptions.postData
}
}
else{
fetchWithAuthData = {
method: controlUrlMethod || controlOptions.method,
digestAuth: hasDigestAuthEnabled
}
}
const theRequest = fetchWithAuthentication(requestUrl,fetchWithAuthData);
theRequest.then(res => res.text())
.then((data) => {
if(doStart){

View File

@ -0,0 +1,18 @@
module.exports = async function(s,config){
s.debugLog('Updating database to 2023-03-11')
const {
alterColumn,
} = require('../utils.js')(s,config)
await alterColumn('Monitors',[
{name: 'path', length: 255, type: 'string'},
])
await alterColumn('Videos',[
{name: 'size', length: 15, type: 'bigInteger'},
])
await alterColumn('Cloud Videos',[
{name: 'size', length: 15, type: 'bigInteger'},
])
await alterColumn('Files',[
{name: 'size', length: 15, type: 'bigInteger'},
])
}

View File

@ -46,7 +46,7 @@ module.exports = function(s,config){
{name: 'ke', length: 50, type: 'string'},
{name: 'mid', length: 100, type: 'string'},
{name: 'name', length: 100, type: 'string'},
{name: 'size', type: 'integer'},
{name: 'size', type: 'bigint'},
{name: 'details', type: 'text'},
{name: 'status', type: 'integer', length: 1, defaultTo: 0},
{name: 'archive', type: 'tinyint', length: 1, defaultTo: 0},
@ -58,7 +58,7 @@ module.exports = function(s,config){
{name: 'ke', length: 50, type: 'string'},
{name: 'mid', length: 100, type: 'string'},
{name: 'ext', type: 'string', length: 10, defaultTo: 'mp4'},
{name: 'size', type: 'integer'},
{name: 'size', type: 'bigint'},
{name: 'status', type: 'tinyint', length: 1, defaultTo: 0},
{name: 'archive', type: 'tinyint', length: 1, defaultTo: 0},
{name: 'objects', length: 510, type: 'string'},
@ -75,7 +75,7 @@ module.exports = function(s,config){
{name: 'ke', length: 50, type: 'string'},
{name: 'mid', length: 100, type: 'string'},
{name: 'href', length: 50, type: 'text'},
{name: 'size', type: 'integer'},
{name: 'size', type: 'bigint'},
{name: 'details', type: 'text'},
{name: 'status', type: 'integer', length: 1, defaultTo: 0},
{name: 'archive', type: 'tinyint', length: 1, defaultTo: 0},
@ -112,7 +112,7 @@ module.exports = function(s,config){
{name: 'mid', length: 100, type: 'string'},
{name: 'filename', length: 50, type: 'string'},
{name: 'time', type: 'timestamp', defaultTo: currentTimestamp()},
{name: 'size', type: 'integer'},
{name: 'size', type: 'bigint'},
{name: 'archive', length: 1, type: 'tinyint', defaultTo: 0},
{name: 'saveDir', length: 255, type: 'string'},
{name: 'details', type: 'text'},
@ -127,7 +127,7 @@ module.exports = function(s,config){
{name: 'href', type: 'text'},
{name: 'filename', length: 50, type: 'string'},
{name: 'time', type: 'timestamp', defaultTo: currentTimestamp()},
{name: 'size', type: 'integer'},
{name: 'size', type: 'bigint'},
{name: 'details', type: 'text'},
]);
await createTable('Logs',[
@ -181,6 +181,7 @@ module.exports = function(s,config){
// additional requirements for older installs
await require('./migrate/2022-08-22.js')(s,config)
await require('./migrate/2022-12-18.js')(s,config)
await require('./migrate/2023-03-11.js')(s,config)
delete(s.preQueries)
}
}

View File

@ -442,6 +442,23 @@ module.exports = function(s,config){
}
}
}
async function alterColumn(tableName,columns){
try{
for (let i = 0; i < columns.length; i++) {
const column = columns[i]
if(!column)return;
await s.databaseEngine.schema.alterTable(tableName, table => {
let action = table[column.type](column.name,column.length)
if(column.defaultTo !== null && column.defaultTo !== undefined){
action = action.defaultTo(column.defaultTo)
}
action.alter()
})
}
}catch(err){
s.debugLog(err)
}
}
async function createTable(tableName,columns,onSuccess){
try{
const exists = await s.databaseEngine.schema.hasTable(tableName)
@ -478,6 +495,7 @@ module.exports = function(s,config){
sqlQueryBetweenTimesWithPermissions: sqlQueryBetweenTimesWithPermissions,
currentTimestamp,
createTable,
alterColumn,
addColumn,
isMySQL,
}

View File

@ -15,7 +15,7 @@ module.exports = (s,config,lang) => {
const acceptableOperators = ['indexOf','!indexOf','===','!==','>=','>','<','<=']
// Event Filters />
const {
splitForFFPMEG
splitForFFMPEG
} = require('../ffmpeg/utils.js')(s,config,lang)
const {
moveCameraPtzToMatrix
@ -556,7 +556,7 @@ module.exports = (s,config,lang) => {
s.debugLog(ffmpegCommand)
activeMonitor.eventBasedRecording.process = spawn(
config.ffmpegDir,
splitForFFPMEG(ffmpegCommand)
splitForFFMPEG(ffmpegCommand)
)
activeMonitor.eventBasedRecording.process.stderr.on('data',function(data){
s.userLog(d,{

View File

@ -5,7 +5,7 @@ module.exports = async (s,config,lang,onFinish) => {
const {
sanitizedFfmpegCommand,
createPipeArray,
splitForFFPMEG,
splitForFFMPEG,
checkForWindows,
checkForUnix,
checkStaticBuilds,
@ -66,7 +66,7 @@ module.exports = async (s,config,lang,onFinish) => {
//hold ffmpeg command for log stream
activeMonitor.ffmpeg = sanitizedFfmpegCommand(e,ffmpegCommandString)
//clean the string of spatial impurities and split for spawn()
const ffmpegCommandParsed = splitForFFPMEG(ffmpegCommandString)
const ffmpegCommandParsed = splitForFFMPEG(ffmpegCommandString)
try{
fs.rmSync(e.sdir + 'cmd.txt')
}catch(err){

View File

@ -38,7 +38,7 @@ module.exports = (s,config,lang) => {
activeProbes[auth] = 1
var stderr = ''
var stdout = ''
const probeCommand = splitForFFPMEG(`${customInput ? customInput + ' ' : ''}-analyzeduration 10000 -probesize 10000 -v quiet -print_format json -show_format -show_streams -i "${url}"`)
const probeCommand = splitForFFMPEG(`${customInput ? customInput + ' ' : ''}-analyzeduration 10000 -probesize 10000 -v quiet -print_format json -show_format -show_streams -i "${url}"`)
var processTimeout = null
var ffprobeLocation = config.ffmpegDir.split('/')
ffprobeLocation[ffprobeLocation.length - 1] = 'ffprobe'
@ -184,7 +184,7 @@ module.exports = (s,config,lang) => {
}
return stdioPipes
}
const splitForFFPMEG = function(ffmpegCommandAsString) {
const splitForFFMPEG = function(ffmpegCommandAsString) {
return ffmpegCommandAsString.replace(/\s+/g,' ').trim().match(/\\?.|^$/g).reduce((p, c) => {
if(c === '"'){
p.quote ^= 1;
@ -378,7 +378,7 @@ Run "npm install ffbinaries" to get this static FFmpeg downloader.`
validateDimensions: validateDimensions,
sanitizedFfmpegCommand: sanitizedFfmpegCommand,
createPipeArray: createPipeArray,
splitForFFPMEG: splitForFFPMEG,
splitForFFMPEG: splitForFFMPEG,
checkForWindows: checkForWindows,
checkForUnix: checkForUnix,
checkForNpmStatic: checkForNpmStatic,

View File

@ -12,7 +12,7 @@ module.exports = function(s,config,lang){
asyncSetTimeout,
} = require('./basic/utils.js')(process.cwd(),config)
const {
splitForFFPMEG,
splitForFFMPEG,
} = require('./ffmpeg/utils.js')(s,config,lang)
const {
processKill,
@ -194,7 +194,7 @@ module.exports = function(s,config,lang){
var iconImageFile = streamDir + 'icon.jpg'
const snapRawFilters = monitor.details.cust_snap_raw
if(snapRawFilters)outputOptions.push(snapRawFilters);
var ffmpegCmd = splitForFFPMEG(`-y -loglevel warning ${isDetectorStream ? '-live_start_index 2' : ''} -re ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f image2 -an -frames:v 1 "${temporaryImageFile}"`)
var ffmpegCmd = splitForFFMPEG(`-y -loglevel warning ${isDetectorStream ? '-live_start_index 2' : ''} -re ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f image2 -an -frames:v 1 "${temporaryImageFile}"`)
try{
await fs.promises.mkdir(streamDir, {recursive: true}, (err) => {s.debugLog(err)})
}catch(err){
@ -316,7 +316,7 @@ module.exports = function(s,config,lang){
//not exist
var cat = 'cat '+copiedItems.join(' ')+' > '+allts
exec(cat,function(){
var merger = spawn(config.ffmpegDir,splitForFFPMEG(('-re -i '+allts+' -acodec copy -vcodec copy -t 00:00:' + videoLength + ' '+pathDir+mergedFile)))
var merger = spawn(config.ffmpegDir,splitForFFMPEG(('-re -i '+allts+' -acodec copy -vcodec copy -t 00:00:' + videoLength + ' '+pathDir+mergedFile)))
merger.stderr.on('data',function(data){
s.userLog(monitor,{type:"Buffer Merge",msg:data.toString()})
})
@ -405,7 +405,7 @@ module.exports = function(s,config,lang){
ke: groupKey,
mid: '$USER'
},{type:lang['Videos Merge'],msg:mergedFile})
var merger = spawn(config.ffmpegDir,splitForFFPMEG(('-re -loglevel warning -i ' + mergedRawFilepath + ' -acodec copy -vcodec copy ' + mergedFilepath)))
var merger = spawn(config.ffmpegDir,splitForFFMPEG(('-re -loglevel warning -i ' + mergedRawFilepath + ' -acodec copy -vcodec copy ' + mergedFilepath)))
merger.stderr.on('data',function(data){
s.userLog({
ke: groupKey,

View File

@ -16,7 +16,7 @@ module.exports = (s,config,lang) => {
createWarningsForConfiguration,
buildMonitorConfigPartialFromWarnings,
createPipeArray,
splitForFFPMEG,
splitForFFMPEG,
sanitizedFfmpegCommand,
} = require('../ffmpeg/utils.js')(s,config,lang)
const {
@ -220,7 +220,7 @@ module.exports = (s,config,lang) => {
})
}
const temporaryImageFile = streamDir + s.gid(5) + '.jpg'
const ffmpegCmd = splitForFFPMEG(`-y -loglevel warning -re ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f image2 -an -frames:v 1 "${temporaryImageFile}"`)
const ffmpegCmd = splitForFFMPEG(`-y -loglevel warning -re ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f image2 -an -frames:v 1 "${temporaryImageFile}"`)
const snapProcess = spawn('ffmpeg',ffmpegCmd,{detached: true})
snapProcess.stderr.on('data',function(data){
// s.debugLog(data.toString())
@ -306,7 +306,7 @@ module.exports = (s,config,lang) => {
});
const ffmpegCommandString = ffmpegCommand.join(' ')
activeMonitor.ffmpegSubstream = sanitizedFfmpegCommand(e,ffmpegCommandString)
const ffmpegCommandParsed = splitForFFPMEG(ffmpegCommandString)
const ffmpegCommandParsed = splitForFFMPEG(ffmpegCommandString)
activeMonitor.subStreamChannel = channelNumber;
s.userLog({
ke: groupKey,

View File

@ -42,7 +42,6 @@ module.exports = function (s, config, lang, getSnapshot) {
s.debugLog(result);
});
})
console.log(sendBody)
} catch (err) {
s.debugLog(err)
s.userLog(

View File

@ -4,6 +4,7 @@ const unzipper = require('unzipper')
const spawn = require('child_process').spawn
const exec = require('child_process').execSync
const treekill = require('tree-kill');
const marked = require('marked').parse;
const {
Worker
} = require('worker_threads');
@ -342,6 +343,20 @@ module.exports = async (s,config,lang,app,io,currentUse) => {
}
})
}
async function getPluginReadme(name,asHTML){
const modulePath = getModulePath(name)
const readmePath = modulePath + 'README.md'
let readmeData = lang['No README found']
try{
readmeData = await fs.promises.readFile(readmePath,'utf8')
}catch(err){
console.log(err)
}
if(asHTML){
readmeData = marked(readmeData)
}
return readmeData
}
/**
* API : Superuser : Custom Auto Load Package Download.
*/
@ -549,6 +564,16 @@ module.exports = async (s,config,lang,app,io,currentUse) => {
s.closeJsonResponse(res,{ok: true})
},res,req)
})
/**
* API : Superuser : Get Plugin README
*/
app.get(config.webPaths.superApiPrefix+':auth/plugins/readme/:pluginName', async (req,res) => {
s.superAuth(req.params, async (resp) => {
const name = req.params.pluginName
const readme = await getPluginReadme(name,true);
s.closeJsonResponse(res,{ok: true, readme: readme})
},res,req)
})
s.onProcessReady(async () => {
// Initialize Modules on Start
await initializeAllModules();

View File

@ -9,7 +9,7 @@ module.exports = function(s,config,lang,app,io){
sendTimelapseFrameToMasterNode,
} = require('./childNode/childUtils.js')(s,config,lang)
const {
splitForFFPMEG,
splitForFFMPEG,
} = require('./ffmpeg/utils.js')(s,config,lang)
const {
getFileDirectory,
@ -49,7 +49,7 @@ module.exports = function(s,config,lang,app,io){
size: fileStats.size,
time: timeNow
}
if(config.childNodes.enabled === true && config.childNodes.mode === 'child' && config.childNodes.host){
if(config.childNodes.enabled === true && config.childNodes.mode === 'child' && config.childNodes.host && config.dropTimeLapseFrames != true){
var currentDate = s.formattedTime(timeNow,'YYYY-MM-DD')
const childNodeData = {
ke: e.ke,
@ -60,7 +60,7 @@ module.exports = function(s,config,lang,app,io){
queryInfo: queryInfo
}
sendTimelapseFrameToMasterNode(filePath,childNodeData)
}else{
}else if (config.dropTimeLapseFrames != true ){
s.insertTimelapseFrameDatabaseRow(e,queryInfo,filePath)
}
}
@ -239,7 +239,7 @@ module.exports = function(s,config,lang,app,io){
const numberOfFrames = framesAccepted.length
const commandString = `-y -threads 1 -re -f concat -safe 0 -r ${framesPerSecond} -i "${concatListFile}" -q:v 1 -c:v libx264 -preset ultrafast -r ${framesPerSecond} "${finalMp4OutputLocation}"`
s.debugLog("ffmpeg",commandString)
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFPMEG(commandString))
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFMPEG(commandString))
videoBuildProcess.stdout.on('data',function(data){
s.debugLog('stdout',finalMp4OutputLocation,data.toString())
})

View File

@ -70,6 +70,9 @@ module.exports = function(s,config,lang){
if(!userDetails.whcs_endpoint ){
userDetails.whcs_endpoint = 's3.wasabisys.com'
}
if(userDetails.whcs_endpoint.indexOf('://') === -1){
userDetails.whcs_endpoint = `https://${userDetails.whcs_endpoint}`
}
s.group[e.ke].whcs = new S3Client({
endpoint: userDetails.whcs_endpoint,
credentials: {

View File

@ -4,7 +4,7 @@ const async = require('async');
module.exports = (s,config,lang) => {
const {
ffprobe,
splitForFFPMEG,
splitForFFMPEG,
} = require('../ffmpeg/utils.js')(s,config,lang)
const {
copyFile,
@ -257,7 +257,7 @@ module.exports = (s,config,lang) => {
const finalMp4OutputLocation = options.output
const commandString = `-y -threads 1 -f concat -safe 0 -i "${concatListFile}" -c:v copy -an -preset ultrafast "${finalMp4OutputLocation}"`
s.debugLog("stitchMp4Files",commandString)
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFPMEG(commandString))
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFMPEG(commandString))
videoBuildProcess.stdout.on('data',function(data){
s.debugLog('stdout',finalMp4OutputLocation,data.toString())
})
@ -286,7 +286,7 @@ module.exports = (s,config,lang) => {
const outputFilePath = `${videoFolder}${tempFilename}`
const commandString = `-y -threads 1 -re -i "${inputFilePath}" -c:v copy -c:a copy -preset ultrafast "${outputFilePath}"`
fixingAlready[fixingId] = true
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFPMEG(commandString))
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFMPEG(commandString))
videoBuildProcess.stdout.on('data',function(data){
s.debugLog('stdout',outputFilePath,data.toString())
})
@ -409,7 +409,7 @@ module.exports = (s,config,lang) => {
oldName: filename,
name: finalFilename,
},'GRP_'+groupKey);
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFPMEG(commandString))
const videoBuildProcess = spawn(config.ffmpegDir,splitForFFMPEG(commandString))
videoBuildProcess.stdout.on('data',function(data){
s.debugLog('stdout',outputFilePath,data.toString())
})

84
package-lock.json generated
View File

@ -29,13 +29,13 @@
"jsonfile": "^3.0.1",
"knex": "^0.21.21",
"ldapauth-fork": "^5.0.2",
"marked": "^4.3.0",
"moment": "^2.29.4",
"mp4frag": "^0.2.0",
"mp4frag": "^0.6.0",
"mqtt": "^4.3.7",
"mysql": "^2.18.1",
"node-abort-controller": "^3.0.1",
"node-fetch": "^2.6.7",
"shinobi-node-moving-things-tracker": "^0.9.1",
"node-onvif-events": "^2.0.5",
"node-ssh": "^12.0.4",
"node-telegram-bot-api": "^0.58.0",
@ -46,6 +46,7 @@
"pixel-change": "^1.1.0",
"pushover-notifications": "^1.2.2",
"sat": "^0.7.1",
"shinobi-node-moving-things-tracker": "^0.9.1",
"shinobi-onvif": "0.1.9",
"shinobi-sound-detection": "^0.1.13",
"shinobi-zwave": "^1.0.11",
@ -4837,6 +4838,17 @@
"node": ">=0.10.0"
}
},
"node_modules/marked": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
@ -4969,9 +4981,12 @@
}
},
"node_modules/mp4frag": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/mp4frag/-/mp4frag-0.2.0.tgz",
"integrity": "sha512-zrLws5vFuUvaivVXu4ZPg7fdJynSbcIT6kI00okZ+jCvxqMIs6zhhh7sw16BE+lL1OD6RyCsFgJEdzxZaeb5fQ=="
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/mp4frag/-/mp4frag-0.6.0.tgz",
"integrity": "sha512-MvBAaWkW94SSpam/QsCmbMi7+ZY2YHzAjj6Uno7AZ6qxH7gZstN+L3jFopdN5F3/5mRK25gvA4k0DVpCbDe7+g==",
"engines": {
"node": ">=10"
}
},
"node_modules/mqtt": {
"version": "4.3.7",
@ -5226,20 +5241,6 @@
"node": ">= 6.13.0"
}
},
"node_modules/node-moving-things-tracker": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/node-moving-things-tracker/-/node-moving-things-tracker-0.9.1.tgz",
"integrity": "sha512-JVa+DbQRgOsOcfIIxhw3kTUfO407RdXnVqNgPvAlhUHu7PWziUah+MuTcaN4rRktBicj/l2scI64a2crBgrzKw==",
"dependencies": {
"lodash.isequal": "^4.5.0",
"minimist": "^1.2.0",
"munkres-js": "^1.2.2",
"uuid": "^3.2.1"
},
"bin": {
"shinobi-node-moving-things-tracker": "main.js"
}
},
"node_modules/node-onvif-events": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/node-onvif-events/-/node-onvif-events-2.0.5.tgz",
@ -6329,6 +6330,19 @@
"resolved": "https://registry.npmjs.org/shell-escape/-/shell-escape-0.2.0.tgz",
"integrity": "sha1-aP0CXrBJC09WegJ/C/IkgLX4QTM="
},
"node_modules/shinobi-node-moving-things-tracker": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/shinobi-node-moving-things-tracker/-/shinobi-node-moving-things-tracker-0.9.1.tgz",
"integrity": "sha512-pcI/IJ9D87RJiTGEsBYvtb2FTQXkHCkuyv6dYaXB5KVpnhQJGyf37BvAbvCNuCJmlffBGTMClY2dg84ZwHl/Ow==",
"dependencies": {
"lodash.isequal": "^4.5.0",
"minimist": "^1.2.0",
"munkres-js": "^1.2.2"
},
"bin": {
"shinobi-node-moving-things-tracker": "main.js"
}
},
"node_modules/shinobi-onvif": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/shinobi-onvif/-/shinobi-onvif-0.1.9.tgz",
@ -11490,6 +11504,11 @@
"object-visit": "^1.0.0"
}
},
"marked": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
"integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A=="
},
"md5": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
@ -11589,9 +11608,9 @@
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
},
"mp4frag": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/mp4frag/-/mp4frag-0.2.0.tgz",
"integrity": "sha512-zrLws5vFuUvaivVXu4ZPg7fdJynSbcIT6kI00okZ+jCvxqMIs6zhhh7sw16BE+lL1OD6RyCsFgJEdzxZaeb5fQ=="
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/mp4frag/-/mp4frag-0.6.0.tgz",
"integrity": "sha512-MvBAaWkW94SSpam/QsCmbMi7+ZY2YHzAjj6Uno7AZ6qxH7gZstN+L3jFopdN5F3/5mRK25gvA4k0DVpCbDe7+g=="
},
"mqtt": {
"version": "4.3.7",
@ -11782,17 +11801,6 @@
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
"integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
},
"shinobi-node-moving-things-tracker": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/node-moving-things-tracker/-/node-moving-things-tracker-0.9.1.tgz",
"integrity": "sha512-JVa+DbQRgOsOcfIIxhw3kTUfO407RdXnVqNgPvAlhUHu7PWziUah+MuTcaN4rRktBicj/l2scI64a2crBgrzKw==",
"requires": {
"lodash.isequal": "^4.5.0",
"minimist": "^1.2.0",
"munkres-js": "^1.2.2",
"uuid": "^3.2.1"
}
},
"node-onvif-events": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/node-onvif-events/-/node-onvif-events-2.0.5.tgz",
@ -12643,6 +12651,16 @@
"resolved": "https://registry.npmjs.org/shell-escape/-/shell-escape-0.2.0.tgz",
"integrity": "sha1-aP0CXrBJC09WegJ/C/IkgLX4QTM="
},
"shinobi-node-moving-things-tracker": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/shinobi-node-moving-things-tracker/-/shinobi-node-moving-things-tracker-0.9.1.tgz",
"integrity": "sha512-pcI/IJ9D87RJiTGEsBYvtb2FTQXkHCkuyv6dYaXB5KVpnhQJGyf37BvAbvCNuCJmlffBGTMClY2dg84ZwHl/Ow==",
"requires": {
"lodash.isequal": "^4.5.0",
"minimist": "^1.2.0",
"munkres-js": "^1.2.2"
}
},
"shinobi-onvif": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/shinobi-onvif/-/shinobi-onvif-0.1.9.tgz",

View File

@ -35,13 +35,13 @@
"jsonfile": "^3.0.1",
"knex": "^0.21.21",
"ldapauth-fork": "^5.0.2",
"marked": "^4.3.0",
"moment": "^2.29.4",
"mp4frag": "^0.2.0",
"mp4frag": "^0.6.0",
"mqtt": "^4.3.7",
"mysql": "^2.18.1",
"node-abort-controller": "^3.0.1",
"node-fetch": "^2.6.7",
"shinobi-node-moving-things-tracker": "^0.9.1",
"node-onvif-events": "^2.0.5",
"node-ssh": "^12.0.4",
"node-telegram-bot-api": "^0.58.0",
@ -52,6 +52,7 @@
"pixel-change": "^1.1.0",
"pushover-notifications": "^1.2.2",
"sat": "^0.7.1",
"shinobi-node-moving-things-tracker": "^0.9.1",
"shinobi-onvif": "0.1.9",
"shinobi-sound-detection": "^0.1.13",
"shinobi-zwave": "^1.0.11",

View File

@ -135,14 +135,14 @@ module.exports = function(s,config,lang,io){
}
},
"ffmpeg.js" : {
splitForFFPMEG : function(next){
splitForFFMPEG : function(next){
var expectedResult = [
'flag1',
'flag2',
'fl ag3',
]
var testResult = s.splitForFFPMEG('flag1 flag2 "fl ag3"')
checkResult('Internal Function : splitForFFPMEG',JSON.stringify(expectedResult),JSON.stringify(testResult))
var testResult = s.splitForFFMPEG('flag1 flag2 "fl ag3"')
checkResult('Internal Function : splitForFFMPEG',JSON.stringify(expectedResult),JSON.stringify(testResult))
next()
},
"ffmpeg" : function(next){

View File

@ -40,7 +40,7 @@
#powerVideo .videoPlayer .videoPlayer-detection-info {
position: absolute;
padding: 20px 10px 20px 10px;
/* height: 100%; */
height: 100%;
width: 100%;
top: 0;
left: 0;
@ -183,3 +183,9 @@
#powerVideo .vis-labelset .vis-label {
color: #fff;
}
#powerVideo .videoPlayer-detection-info-buttons {
position: absolute;
top: 5px;
right: 5px;
}

View File

@ -7,3 +7,8 @@
border-radius: 5px;
max-height: 300px;
}
.readme-view {
padding: 1rem 2rem;
border: 1px solid #009dff;
border-radius: 10px;
}

View File

@ -875,11 +875,17 @@ function downloadJSON(jsonData,filename){
.attr('download',filename)
[0].click()
}
function downloadFile(downloadUrl,fileName){
var a = document.createElement('a')
a.href = downloadUrl
a.download = fileName
a.click()
function downloadFile(url,filename) {
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function getFilenameFromUrl(url) {
const parts = url.split("/");
return parts[parts.length - 1];
}
function notifyIfActionFailed(data){
if(data.ok === false){
@ -986,7 +992,7 @@ function convertJsonToAccordionHtml(theJson){
keys.forEach((key) => {
var value = innerJson[key]
var isObject = typeof value === 'object' || typeof value === 'array'
if(value)html += `<li><a class="badge btn btn-sm ${isObject ? `toggle-accordion-list btn-primary` : `btn-default`}"><i class="fa fa-${isObject ? `plus` : `circle`}"></i></a> ${key} ${isObject ? recurseJson(value,true) : `: ${value}`}</li>`
if(value === 0 || value === false || value)html += `<li><a class="badge btn btn-sm ${isObject ? `toggle-accordion-list btn-primary` : `btn-default`}"><i class="fa fa-${isObject ? `plus` : `circle`}"></i></a> ${key} ${isObject ? recurseJson(value,true) : `: ${value}`}</li>`
})
html += `</ul>`
return html

View File

@ -58,8 +58,8 @@ function generateDefaultMonitorSettings(){
"hwaccel_vcodec": "",
"hwaccel_device": "",
"use_coprocessor": null,
"stream_type": "hls",
"stream_flv_type": "ws",
"stream_type": "mp4",
"stream_flv_type": "http",
"stream_flv_maxLatency": "",
"stream_mjpeg_clients": "",
"stream_vcodec": "copy",

View File

@ -456,12 +456,7 @@ $(document).ready(function(e){
motionMeterProgressBarTextBox.text('0')
}
function resetWidthForActiveVideoPlayers(){
var numberOfMonitors = 0
powerVideoMonitorViewsElement.find(`.videoPlayer .videoNow`).each(function(n,videoEl){
if(videoEl.currentTime > 0)numberOfMonitors += 1
})
var widthOfBlock = 100 / numberOfMonitors
powerVideoMonitorViewsElement.find('.videoPlayer').css('width',`${widthOfBlock}%`)
powerVideoMonitorViewsElement.find('.videoPlayer').css('width',`49.8%`)
}
function loadVideoIntoMonitorSlot(video,selectedTime){
if(!video)return
@ -472,12 +467,15 @@ $(document).ready(function(e){
// if(numberOfMonitors > 3)numberOfMonitors = 3 //start new row after 3
if(numberOfMonitors == 1)numberOfMonitors = 2 //make single monitor not look like a doofus
if(timeToStartAt < 0)timeToStartAt = 0
var widthOfBlock = 100 / numberOfMonitors
var videoContainer = powerVideoMonitorViewsElement.find(`.videoPlayer[data-mid=${video.mid}] .videoPlayer-buffers`)
if(videoContainer.length === 0){
if(!monitorSlotPlaySpeeds)monitorSlotPlaySpeeds[video.mid] = {}
powerVideoMonitorViewsElement.append(`<div class="videoPlayer" style="width:${widthOfBlock}%;max-width:500px" data-mid="${video.mid}">
powerVideoMonitorViewsElement.append(`<div class="videoPlayer" style="width:49.8%;max-width:500px;min-width:250px;" data-mid="${video.mid}">
<div class="videoPlayer-detection-info">
<div class="videoPlayer-detection-info-buttons btn-group">
<a powerVideo-control="downloadVideo" class="btn btn-sm btn-success"><i class="fa fa-download"></i></a>
<a powerVideo-control="openVideoPlayer" class="btn btn-sm btn-default"><i class="fa fa-external-link"></i></a>
</div>
<canvas style="height:400px"></canvas>
</div>
<div class="videoPlayer-stream-objects"></div>
@ -488,7 +486,7 @@ $(document).ready(function(e){
</div>`)
videoContainer = powerVideoMonitorViewsElement.find(`.videoPlayer[data-mid=${video.mid}] .videoPlayer-buffers`)
}else{
powerVideoMonitorViewsElement.find('.videoPlayer').css('width',`${widthOfBlock}%`)
powerVideoMonitorViewsElement.find('.videoPlayer').css('width',`49.8%`)
}
var videoCurrentNow = videoContainer.find('.videoNow')
var videoCurrentAfter = videoContainer.find('.videoAfter')
@ -572,9 +570,15 @@ $(document).ready(function(e){
var selectedMonitors = Object.keys(form).filter(key => form[key] == '1')
return selectedMonitors
}
function getActiveVideoInSlot(monitorId){
return powerVideoMonitorViewsElement.find(`.videoPlayer[data-mid="${monitorId}"] video.videoNow`)[0]
}
function getAllActiveVideosInSlots(){
return powerVideoMonitorViewsElement.find('video.videoNow')
}
function getActiveVideoRow(monitorId){
return currentlyPlayingVideos[monitorId]
}
function pauseAllSlots(){
getAllActiveVideosInSlots().each(function(n,video){
if(!video.paused)video.pause()
@ -675,6 +679,27 @@ $(document).ready(function(e){
})
lastPowerVideoSelectedMonitors = ([]).concat(monitorIdsSelectedNow || [])
}
function downloadPlayingVideo(video){
if(video.currentSrc){
var filename = getFilenameFromUrl(video.currentSrc)
downloadFile(video.currentSrc,filename)
}
}
function downloadAllPlayingVideos(){
getAllActiveVideosInSlots().each(function(n,video){
downloadPlayingVideo(video)
})
}
function openVideoPlayerTabFromViewer(el){
var monitorId = el.attr('data-mid') || el.parents('[data-mid]').attr('data-mid')
var video = getActiveVideoRow(monitorId)
createVideoPlayerTab(video)
}
function downloadPlayingVideoTabFromViewer(el){
var monitorId = el.attr('data-mid') || el.parents('[data-mid]').attr('data-mid')
var video = getActiveVideoInSlot(monitorId)
downloadPlayingVideo(video)
}
powerVideoMonitorsListElement.on('change','input',onPowerVideoSettingsChange);
powerVideoVideoLimitElement.change(onPowerVideoSettingsChange);
powerVideoEventLimitElement.change(onPowerVideoSettingsChange);
@ -689,6 +714,17 @@ $(document).ready(function(e){
var el = $(this)
var controlType = el.attr('powerVideo-control')
switch(controlType){
// single video affected
case'downloadVideo':
downloadPlayingVideoTabFromViewer(el)
break;
case'openVideoPlayer':
openVideoPlayerTabFromViewer(el)
break;
// all videos affected
case'downloadPlaying':
downloadAllPlayingVideos()
break;
case'toggleMute':
toggleMute()
break;

View File

@ -1,321 +1,376 @@
$(document).ready(function(){
var schema = {
"title": "Main Configuration",
"type": "object",
"properties": {
"debugLog": {
$(document).ready(function () {
var schema = {
"title": "Main Configuration",
"type": "object",
"properties": {
"debugLog": {
"title": "Enable Debug Log",
"type": "boolean",
"default": false
},
"subscriptionId": {
"title": "Fill in subscription ID",
"type": "string",
"default": null
},
"port": {
"title": "Server port",
"type": "integer",
"default": 8080
},
"passwordType": {
"title": "Password type",
"type": "string",
"enum": [
"sha256",
"sha512",
"md5"
],
"default": "sha256"
},
"addStorage": {
"type": "array",
"format": "table",
"title": "Additional Storage",
"description": "Separate storage locations that can be set for different monitors.",
"uniqueItems": true,
"items": {
"type": "object",
"title": "Storage Array",
"properties": {
"name": {
"type": "string",
},
"path": {
"type": "string",
"default": "__DIR__/videos2"
}
}
},
"default": [
{
"name": "second",
"path": "__DIR__/videos2"
}
]
},
"plugins": {
"type": "array",
"format": "table",
"title": "Plugins",
"descripton": "Elaborate Plugin connection settings.",
"uniqueItems": true,
"items": {
"type": "object",
"title": "Plugin",
"properties": {
"plug": {
"type": "string",
"default": "pluginName"
},
"key": {
"type": "string"
},
"mode": {
"type": "string",
"enum": [
"host",
"client"
],
"default": "client"
},
"https": {
"type": "boolean",
"descripton": "Only for Host mode.",
"default": false
},
"host": {
"type": "string",
"descripton": "Only for Host mode.",
"default": "localhost"
},
"port": {
"type": "integer",
"descripton": "Only for Host mode.",
"default": 8082
},
"type": {
"type": "string",
"default": "detector"
}
}
}
},
"pluginKeys": {
"type": "object",
"format": "table",
"title": "Plugin Keys",
"description": "Quick client connection setup for plugins. Just add the plugin key to make it ready for incoming connections.",
"uniqueItems": true,
"items": {
"type": "object",
"title": "Plugin Key",
"properties": {}
}
},
"db": {
"type": "object",
"format": "table",
"title": "Database Options",
"description": "Credentials to connect to where detailed information is stored.",
"properties": {
"host": {
"title": "Hostname / IP",
"type": "string",
"default": "127.0.0.1"
},
"user": {
"title": "Username",
"type": "string",
"default": "majesticflame"
},
"password": {
"title": "Password",
"type": "string",
"default": ""
},
"database": {
"type": "string",
"default": "ccio"
},
"port": {
"type": "integer",
"default": 3306
}
},
"default": {
"host": "127.0.0.1",
"user": "majesticflame",
"password": "",
"database": "ccio",
"port": 3306
}
},
"cron": {
"type": "object",
"format": "table",
"title": "CRON Options",
"properties": {
"key": {
"type": "string",
},
"deleteOld": {
"type": "boolean",
"description": "cron will delete videos older than Max Number of Days per account.",
"default": true
},
"deleteNoVideo": {
"type": "boolean",
"description": "cron will delete SQL rows that it thinks have no video files.",
"default": true
},
"deleteOverMax": {
"type": "boolean",
"description": "cron will delete files that are over the set maximum storage per account.",
"default": true
},
}
},
"mail": {
"type": "object",
"format": "table",
"title": "Email Options",
"properties": {
"service": {
"type": "string",
},
"host": {
"type": "string",
},
"auth": {
"type": "object",
"properties": {
"user": {
"type": "string",
},
"pass": {
"type": "string",
},
},
},
"secure": {
"type": "boolean",
"default": false
},
"subscriptionId": {
"type": "string",
"ignoreTLS": {
"type": "boolean",
},
"port": {
"type": "integer",
"default": 8080
},
"passwordType": {
"type": "string",
"enum": [
"sha256",
"sha512",
"md5"
],
"default": "sha256"
},
"addStorage": {
"type": "array",
"format": "table",
"title": "Additional Storage",
"description": "Separate storage locations that can be set for different monitors.",
"uniqueItems": true,
"items": {
"type": "object",
"title": "Storage Array",
"properties": {
"name": {
"type": "string",
},
"path": {
"type": "string",
"default": "__DIR__/videos2"
}
}
"requireTLS": {
"type": "boolean",
},
"default": [
{
"name": "second",
"path": "__DIR__/videos2"
}
]
},
"plugins": {
"type": "array",
"format": "table",
"title": "Plugins",
"descripton": "Elaborate Plugin connection settings.",
"uniqueItems": true,
"items": {
"type": "object",
"title": "Plugin",
"properties": {
"plug": {
"type": "string",
"default": "pluginName"
},
"key": {
"type": "string"
},
"mode": {
"type": "string",
"enum": [
"host",
"client"
],
"default": "client"
},
"https": {
"type": "boolean",
"descripton": "Only for Host mode.",
"default": false
},
"host": {
"type": "string",
"descripton": "Only for Host mode.",
"default": "localhost"
},
"port": {
"type": "integer",
"descripton": "Only for Host mode.",
"default": 8082
},
"type": {
"type": "string",
"default": "detector"
}
}
},
"default": [
{
"name": "second",
"path": "__DIR__/videos2"
}
]
},
"pluginKeys": {
"type": "object",
"format": "table",
"title": "Plugin Keys",
"description": "Quick client connection setup for plugins. Just add the plugin key to make it ready for incoming connections.",
"uniqueItems": true,
"items": {
"type": "object",
"title": "Plugin Key",
"properties": {}
"port": {
"type": "integer",
}
},
"db": {
"type": "object",
"format": "table",
"title": "Database Options",
"description": "Credentials to connect to where detailed information is stored.",
"properties": {
"host": {
"type": "string",
"default": "127.0.0.1"
},
"user": {
"type": "string",
"default": "majesticflame"
},
"password": {
"type": "string",
"default": ""
},
"database": {
"type": "string",
"default": "ccio"
},
"port": {
"type": "integer",
"default": 3306
}
},
"default": {
"host": "127.0.0.1",
"user": "majesticflame",
"password": "",
"database": "ccio",
"port":3306
}
},
"cron": {
"type": "object",
"format": "table",
"title": "CRON Options",
"properties": {
"key": {
"type": "string",
},
"deleteOld": {
"type": "boolean",
"description": "cron will delete videos older than Max Number of Days per account.",
"default": true
},
"deleteNoVideo": {
"type": "boolean",
"description": "cron will delete SQL rows that it thinks have no video files.",
"default": true
},
"deleteOverMax": {
"type": "boolean",
"description": "cron will delete files that are over the set maximum storage per account.",
"default": true
},
}
},
"mail": {
"type": "object",
"format": "table",
"title": "Email Options",
"properties": {
"service": {
"type": "string",
},
"host": {
"type": "string",
},
"auth": {
"type": "object",
"properties": {
"user": {
"type": "string",
},
"pass": {
"type": "string",
},
},
},
"secure": {
"type": "boolean",
"default": false
},
"ignoreTLS": {
"type": "boolean",
},
"requireTLS": {
"type": "boolean",
},
"port": {
"type": "integer",
}
}
},
"detectorMergePamRegionTriggers": {
"type": "boolean",
"default": true
},
"doSnapshot": {
"type": "boolean",
"default": true
},
"discordBot": {
"type": "boolean",
"default": false
},
"dropInEventServer": {
"type": "boolean",
"default": false
},
"ftpServer": {
"type": "boolean",
"default": false
},
"oldPowerVideo": {
"type": "boolean",
"default": false
},
"wallClockTimestampAsDefault": {
"type": "boolean",
"default": true
},
"defaultMjpeg": {
"type": "string",
},
"streamDir": {
"type": "string",
},
"videosDir": {
"type": "string",
},
"windowsTempDir": {
"type": "string",
}
},
"detectorMergePamRegionTriggers": {
"type": "boolean",
"default": true
},
"doSnapshot": {
"type": "boolean",
"default": true
},
"discordBot": {
"type": "boolean",
"default": false
},
"dropInEventServer": {
"type": "boolean",
"default": false
},
"ftpServer": {
"type": "boolean",
"default": false
},
"oldPowerVideo": {
"type": "boolean",
"default": false
},
"wallClockTimestampAsDefault": {
"type": "boolean",
"default": true
},
"defaultMjpeg": {
"type": "string",
},
"streamDir": {
"type": "string",
},
"videosDir": {
"type": "string",
},
"windowsTempDir": {
"type": "string",
},
"enableFaceManager": {
"type": "boolean",
"default": false,
"title": "Enable Face Manager",
"description": "Enable / Disable face manager for face recognition plugins in the dashboard."
}
};
}
};
var configurationTab = $('#config')
var configurationForm = configurationTab.find('form')
const configurationTab = $("#config");
const configurationForm = configurationTab.find("form");
// Set default options
JSONEditor.defaults.options.theme = 'bootstrap3';
JSONEditor.defaults.options.iconlib = 'fontawesome4';
const moduleData = {
endpoint: null,
configurationEditor: null
}
// Initialize the editor
var configurationEditor = new JSONEditor(document.getElementById("configForHumans"),{
theme: 'bootstrap3',
schema: schema
const handleGetConfigurationData = data => {
const dataConfig = data.config;
const dataConfigKeys = Object.keys(dataConfig);
const schemaItemsKeys = Object.keys(schema.properties);
const schemaWithoutData = schemaItemsKeys.filter(
(sk) => !dataConfigKeys.includes(sk)
);
const dataWithoutSchema = dataConfigKeys.filter(
(dk) => !schemaItemsKeys.includes(dk)
);
schemaWithoutData.forEach((sk) => {
const schemaItem = schema.properties[sk];
const defaultConfig = schemaItem.default;
data.config[sk] = defaultConfig;
});
function loadConfiguationIntoEditor(){
$.get(superApiPrefix + $user.sessionKey + '/system/configure',function(data){
configurationEditor.setValue(data.config);
})
if (dataWithoutSchema.length > 0) {
dataWithoutSchema.forEach((dk) => {
const schemaItem = {
title: dk,
options: {
hidden: true,
},
};
schema.properties[dk] = schemaItem;
});
// Set default options
JSONEditor.defaults.options.theme = "bootstrap3";
JSONEditor.defaults.options.iconlib = "fontawesome4";
}
// configurationEditor.on("change", function() {
// // Do something...
// });
var submitConfiguration = function(){
var errors = configurationEditor.validate();
console.log(errors.length)
console.log(errors)
if(errors.length === 0) {
var newConfiguration = JSON.stringify(configurationEditor.getValue(),null,3)
var html = '<p>This is a change being applied to the configuration file (conf.json). Are you sure you want to do this? You must restart Shinobi for these changes to take effect. <b>The JSON below is what you are about to save.</b></p>'
html += `<pre>${newConfiguration}</pre>`
$.confirm.create({
title: 'Save Configuration',
body: html,
clickOptions: {
class: 'btn-success',
title: lang.Save,
},
clickCallback: function(){
$.post(superApiPrefix + $user.sessionKey + '/system/configure',{
data: newConfiguration
},function(data){
// console.log(data)
})
}
})
}else{
new PNotify({text:'Invalid JSON Syntax, Cannot Save.',type:'error'})
}
const configurationEditor = new JSONEditor(
document.getElementById("configForHumans"), {
schema: schema,
}
configurationTab.find('.submit').click(function(){
submitConfiguration()
})
configurationForm.submit(function(e){
e.preventDefault()
submitConfiguration()
return false;
})
$.ccio.ws.on('f',function(d){
switch(d.f){
case'init_success':
loadConfiguationIntoEditor()
break;
);
configurationEditor.setValue(data.config);
moduleData.configurationEditor = configurationEditor;
window.configurationEditor = configurationEditor;
};
const handlePostConfigurationData = data => {
// console.log(data);
}
function loadConfiguationIntoEditor(d) {
moduleData.endpoint = `${superApiPrefix}${$user.sessionKey}/system/configure`;
$.get(moduleData.endpoint, handleGetConfigurationData);
}
var submitConfiguration = function () {
var errors = configurationEditor.validate();
console.log(errors.length)
console.log(errors)
if (errors.length === 0) {
var newConfiguration = JSON.stringify(configurationEditor.getValue(), null, 3)
var html = '<p>This is a change being applied to the configuration file (conf.json). Are you sure you want to do this? You must restart Shinobi for these changes to take effect. <b>The JSON below is what you are about to save.</b></p>'
html += `<pre>${newConfiguration}</pre>`
$.confirm.create({
title: 'Save Configuration',
body: html,
clickOptions: {
class: 'btn-success',
title: lang.Save,
},
clickCallback: function () {
const requestData = {
data: newConfiguration
};
$.post(moduleData.endpoint, requestData, handlePostConfigurationData);
}
})
window.configurationEditor = configurationEditor
})
} else {
new PNotify({ text: 'Invalid JSON Syntax, Cannot Save.', type: 'error' });
}
};
configurationTab.find('.submit').click(function () {
submitConfiguration();
});
configurationForm.submit(function (e) {
e.preventDefault();
submitConfiguration();
return false;
});
$.ccio.ws.on("f", d => {
if (d.f === "init_success") {
loadConfiguationIntoEditor();
}
});
window.configurationEditor = configurationEditor;
})

View File

@ -47,6 +47,7 @@ $(document).ready(function(){
<div class="pb-2"><b>${lang['Time Created']} :</b> ${module.created}</div>
<div class="pb-2"><b>${lang['Last Modified']} :</b> ${module.lastModified}</div>
<div class="mb-2">
<a class="btn btn-sm btn-default" plugin-manager-action="readmeToggle">${lang.Notes}</a>
${module.hasInstaller ? `
<a class="btn btn-sm btn-info" plugin-manager-action="install">${lang['Run Installer']}</a>
<a class="btn btn-sm btn-danger" style="display:none" plugin-manager-action="cancelInstall">${lang['Stop']}</a>
@ -74,6 +75,7 @@ $(document).ready(function(){
<button type="button" class="btn btn-sm btn-danger btn-block" plugin-manager-action="command" command="N">${lang.No}</button>
</div>
</div>
<div class="readme-view d-none"></div>
</div>
</div>`)
var newBlock = $(`.card[package-name="${module.name}"]`)
@ -186,6 +188,24 @@ $(document).ready(function(){
objDiv.scrollTop = objDiv.scrollHeight;
},100)
}
function getReadme(packageName){
return new Promise(function(resolve){
$.get(`${superApiPrefix}${$user.sessionKey}/plugins/readme/${packageName}`,function(data){
if(!data.ok)console.error(data);
resolve(data.readme || '')
})
})
}
async function toggleReadme(packageName){
var readmeView = loadedBlocks[packageName].block.find('.readme-view')
var isHidden = readmeView.hasClass('d-none')
if(isHidden){
var readmeHTML = await getReadme(packageName)
readmeView.removeClass('d-none').html(readmeHTML)
}else{
readmeView.addClass('d-none').empty()
}
}
$('body')
.on(`submit`,`[plugin-manager-command-line]`,function(e){
e.preventDefault()
@ -206,6 +226,9 @@ $(document).ready(function(){
var card = el.parents('[package-name]')
var packageName = card.attr('package-name')
switch(action){
case'readmeToggle':
toggleReadme(packageName)
break;
case'run':
var scriptName = el.attr('data-script')
runModuleCommand(packageName,scriptName,function(data){