0.1 Rally Framework

merge-requests/521/merge^2
Moe 2024-10-04 11:56:29 -07:00
parent 76b662a139
commit fb0cfe966a
6 changed files with 285 additions and 1 deletions

View File

@ -89,6 +89,8 @@ require('./libs/ffmpeg.js')(s,config,lang, async () => {
require('./libs/onvifDeviceManager.js')(s,config,lang,app,io)
//alternate logins
require('./libs/auth/logins.js')(s,config,lang,app)
//rally other Shinobi
require('./libs/rally.js')(s,config,lang,app,io)
//on-start actions, daemon(s) starter
await require('./libs/startup.js')(s,config,lang)
//p2p, commander

View File

@ -7904,6 +7904,11 @@ module.exports = function(s,config,lang){
pageOpen: 'onvifDeviceManager',
eval: `!$user.details.sub || $user.details.monitor_create !== 0`,
},
{
icon: 'ravelry',
label: `${lang['Rally']}`,
pageOpen: 'rally',
},
{
icon: 'eyedropper',
label: `${lang['FFprobe']}`,
@ -9220,6 +9225,6 @@ module.exports = function(s,config,lang){
]
},
}
},
}
})
}

View File

@ -18,6 +18,12 @@
"accountEditError": "Account Edit Error",
"Monitor Map": "Monitor Map",
"Geolocation": "Geolocation",
"Configure": "Configure",
"Scan": "Scan",
"Rally": "Rally",
"rallyApiKeyFieldText": "Permissions required : Get Monitors, Edit Monitors, View Streams, View Videos.",
"rallyDescription": "Here you can connect to a separate Shinobi server and add their cameras to this Shinobi server. The connection would be directly from the separate server instead of directly from the cameras.",
"rallyChannelDescription": "Channel to rally from. Default is 0 (Main).",
"Tested on": "Tested on",
"Architecture": "Architecture",
"Operating Systems": "Operating Systems",
@ -354,6 +360,7 @@
"File Type": "File Type",
"Filesize": "Filesize",
"Created": "Created",
"Status": "Status",
"Size": "Size",
"Video Status": "Video Status",
"Custom Auto Load": "Custom Auto Load",
@ -770,6 +777,9 @@
"Install": "Install",
"Disable": "Disable",
"Add All": "Add All",
"Add All (Rallied)": "Add All (Rallied)",
"Add All (Direct)": "Add All (Direct)",
"AddAllRalliedText": "Do you want to rally and add these Monitors from the specified Shinobi server?",
"Name": "Name",
"Skip Ping": "Skip Ping",
"Retry Connection": "Retry Connection <small>Number of times allowed to fail</small>",
@ -1322,6 +1332,7 @@
"Male": "Male",
"Female": "Female",
"Channel": "Channel",
"Channels Available": "Channels Available",
"Stream Key": "Stream Key",
"Server URL": "Server URL",
"Video Bit Rate": "Video Bit Rate",

128
libs/rally.js Normal file
View File

@ -0,0 +1,128 @@
const {
ShinobiAPI,
formatDateTime,
getCameraTemplate,
cleanStringForMonitorId,
} = require('node-shinobi');
module.exports = (s,config,lang,app,io) => {
function getServerInfo(){}
async function getMonitors({ host, groupKey, apiKey }){
const shinobi = new ShinobiAPI(host, apiKey, groupKey);
const monitors = await shinobi.getMonitor();
return monitors
}
/**
* API : Get Monitors
*/
app.post(config.webPaths.apiPrefix+':auth/rally/:ke/getMonitors', function (req,res){
s.auth(req.params, async function(user){
const groupKey = req.params.ke
const asis = s.getPostData(req,'asis') === '1'
const connectionInfo = s.getPostData(req,'connectionInfo',true) || {}
const {
isRestricted,
isRestrictedApiKey,
apiKeyPermissions,
userPermissions,
} = s.checkPermission(user)
if(
isRestrictedApiKey && apiKeyPermissions.get_monitors_disallowed
){
s.closeJsonResponse(res,[]);
return
}
if(!connectionInfo.host || !connectionInfo.groupKey || !connectionInfo.apiKey){
s.closeJsonResponse(res,{ok: false, msg: lang['No Data']});
return
}
const monitors = await getMonitors(connectionInfo) || [];
s.closeJsonResponse(res, monitors);
},res,req);
});
// page structure
config.webBlocksPreloaded.push(`home/rally`)
s.definitions['Rally'] = {
"name": "Rally",
"blocks": {
"Search Settings": {
"id": "rallyConfigure",
"name": lang.Scan,
"blockquote": lang.RallyDescription,
"color": "green",
"section-pre-class": "col-md-4",
isFormGroupGroup: true,
"info": [
{
"name": "host",
"field": lang["Host"],
"placeholder": "http://shinobi_host:8080",
},
{
"name": "groupKey",
"field": lang["Group Key"],
},
{
"name": "apiKey",
"field": lang["API Key"],
"description": lang.rallyApiKeyFieldText,
},
{
"name": "channel",
"field": lang["Channel"],
"description": lang.rallyChannelDescription,
},
{
"fieldType": "btn-group",
"btns": [
{
forForm: true,
"fieldType": "btn",
"class": `btn-success fill mb-3`,
"icon": `search`,
"attribute": `type="submit"`,
"btnContent": `${lang['Scan']}`,
}
]
}
]
},
"Management": {
"id": "rallyManagement",
"noHeader": true,
"color": "blue",
"section-pre-class": "col-md-8",
"info": [
{
"id":"rallyServerInfo",
"fieldType": "div",
"class": "mb-3",
},
{
"fieldType": "btn-group",
"class": "mb-3",
"btns": [
{
"fieldType": "btn",
"class": `btn-success add-all`,
"btnContent": `<i class="fa fa-plus"></i> ${lang['Add All (Rallied)']}`,
},
// {
// "fieldType": "btn",
// "class": `btn-success add-all-direct`,
// "btnContent": `<i class="fa fa-plus"></i> ${lang['Add All (Direct)']}`,
// },
],
},
{
"id":"rallyCameras",
"fieldType": "table",
"attribute": `data-classes="table table-striped"`,
"divContent": ""
}
]
},
}
}
}

127
web/assets/js/bs5.rally.js Normal file
View File

@ -0,0 +1,127 @@
$(document).ready(function(e){
//api window
var foundMonitors = {}
var theWindow = $('#tab-rally')
var theWindowForm = $('#rallyForm')
var tableEl = $('#rallyCameras')
function getMonitors(connectionInfo, asis){
return new Promise((resolve) => {
$.post(`${getApiPrefix('rally')}/getMonitors`,{
connectionInfo,
},function(data){
resolve(data)
})
})
}
function saveMonitor(monitor){
return new Promise((resolve) => {
$.post(`${getApiPrefix('configureMonitor')}/${monitor.mid}`,{
data: JSON.stringify(monitor)
},function(data){
resolve(data)
})
})
}
async function saveMonitors(monitors){
for(monitor of monitors){
await saveMonitor(monitor)
}
}
function channelsAvailable(monitor){
const streams = monitor.streams;
return Object.keys(streams).join(', ')
}
function findRallyTypeFromStreamPath(streamPath) {
let chosenType = null;
if(streamPath.endsWith('m3u8')){
chosenType = 'hls'
}else if(streamPath.endsWith('mp4')){
chosenType = 'mp4'
}
return chosenType;
}
function drawTable(monitors){
var html = ''
foundMonitors = {}
for(monitor of monitors){
var monitorKey = `${monitor.ke}${monitor.mid}`
foundMonitors[monitorKey] = monitor;
html += `
<tr monitor="${monitorKey}">
<td title="${lang['Monitor Name']}">${monitor.name}</td>
<td title="${lang['Monitor ID']}">${monitor.mid}</td>
<td title="${lang.Host}">${monitor.host}</td>
<td title="${lang.Status}">${monitorStatusCodes[monitor.code]}</td>
<td title="${lang['Channels Available']}">${channelsAvailable(monitor)}</td>
<td><a class="add-monitor btn btn-sm btn-success" href="#">${lang.Add}</a></td>
</tr>`
}
tableEl.html(html);
}
function convertToRalliedMonitor(monitor, connectionInfo){
const { host, groupKey, apiKey, channel } = connectionInfo;
let [ hostname, port ] = host.split('://')[1].split(':');
const protocol = host.startsWith('https') ? 'https' : 'http';
hostname = hostname.endsWith('/') ? hostname.substring(0, str.length - 1) : hostname;
const streamPath = monitor.streams[parseInt(channel) || 0];
const rallyType = findRallyTypeFromStreamPath(streamPath);
if(hostname && rallyType){
const autoHostUrl = `${protocol}://${hostname}:${port}${streamPath}`;
monitor.protocol = protocol;
monitor.host = hostname;
monitor.port = port;
monitor.path = streamPath;
monitor.type = rallyType;
monitor.details.auto_host = autoHostUrl;
return monitor
}
return null;
}
function convertToRalliedMonitors(monitors, connectionInfo){
const ralliedMonitors = []
monitors.forEach(monitor => {
var converted = convertToRalliedMonitor(monitor, connectionInfo)
if(converted)ralliedMonitors.push(converted)
});
return ralliedMonitors;
}
function getConnectionInfo(){
const connectionInfo = theWindowForm.serializeObject();
return connectionInfo
}
theWindowForm.submit(async function(e){
e.preventDefault();
const connectionInfo = getConnectionInfo()
const monitors = await getMonitors(connectionInfo)
drawTable(monitors)
return false;
})
theWindow.on('click','.add-monitor',function(e){
e.preventDefault();
const connectionInfo = getConnectionInfo()
const el = $(this).parents('[monitor]')
const monitorKey = el.attr('monitor')
const monitor = foundMonitors[monitorKey]
const convertedMonitor = convertToRalliedMonitor(monitor, connectionInfo)
saveMonitor(convertedMonitor)
})
theWindow.on('click','.add-all',function(e){
const connectionInfo = getConnectionInfo()
const monitors = convertToRalliedMonitors(foundMonitors, connectionInfo)
$.confirm.create({
title: lang['Add All (Rallied)'],
body: lang.AddAllRalliedText,
clickOptions: {
class: 'btn-success',
title: `<i class="fa fa-check"></i> ${lang.Save}`,
},
clickCallback: function(){
saveMonitors(monitors)
}
})
})
// addOnTabOpen('rally', function () {
// })
// addOnTabReopen('rally', function () {
// })
})

View File

@ -0,0 +1,11 @@
<main class="container page-tab dark pt-3" id="tab-rally">
<form id="rallyForm" class="row">
<% Object.keys(define['Rally'].blocks).forEach(function(blockKey) { -%>
<%- include('drawBlock', {
theBlock: define['Rally'].blocks[blockKey],
details: $user.details
}) %>
<% }) -%>
</form>
<script src="<%-window.libURL%>assets/js/bs5.rally.js"></script>
</main>