Shared memory rewrite

This commit is contained in:
Thulinma 2014-04-04 19:50:40 +02:00
parent afcddbfca6
commit cd2fe225c5
81 changed files with 7775 additions and 5411 deletions

File diff suppressed because one or more lines are too long

View file

@ -1,4 +1,4 @@
<html>
<head>
<meta http-equiv='content-type' content='text/html;charset=utf-8' />
<title>MistServer Manager</title>
<title>MistServer MI</title>

5
lsp/jquery.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -14,6 +14,8 @@ function consolelog() {
}
}
var ih = false;
function confirmDelete(question){
return confirm(question);
}
@ -56,6 +58,151 @@ function formatTime(date){
('00' + d.getSeconds()).slice(-2)
].join(':');
}
/**
* Format a time duration to something like "2 days, 00:00:00.000"
* @param ms the duration to format in miliseconds
*/
function formatDuration(ms) {
var secs = Math.floor(ms / 1000), mins = 0;
ms = ms % 1000;
if (secs >= 60) {
mins = Math.floor(secs / 60);
secs = secs % 60;
}
if (mins >= 60) {
var hours = Math.floor(mins / 60);
mins = mins % 60;
}
var string = ('00'+mins).slice(-2)+':'+('00'+secs).slice(-2)+'.'+('000'+ms).slice(-3);
if (hours >= 24) {
var days = Math.floor(hours / 24);
hours = hours % 24;
}
if (hours > 0) {
string = ('00'+hours).slice(-2)+':'+string;
}
if (days > 0) {
string = days+' day'+(days > 1 ? 's' : '')+', '+string
}
return string;
}
/**
* Capitalize the first letter
* @param string the string
*/
function capFirstChar(string) {
if (string.length <= 0) { return ''; }
return string[0].toUpperCase() + string.slice(1);
}
/**
* Flot tick generator for bandwidth
* @param axis the axis
*/
function flotTicksBandwidthAxis(axis) {
var range = axis.max - axis.min;
var delta = range / 4;
var start = axis.min;
if (axis.max < 1024) { // unit: bytes/s
if (delta > 100) { delta = Math.floor(delta/100)*100; start = Math.floor(start/100)*100; } // to lowest 100 bytes/s
else if (delta > 10) { delta = Math.floor(delta/10)*10; start = Math.floor(start/10)*10; } // to lowest 10 bytes/s
}
else if (axis.max < 1048576) { //unit: kiB/s
if (delta > 102400) { delta = Math.floor(delta/102400)*102400; start = Math.floor(start/102400)*102400; } //to lowest 100 kiB/s
else if (delta > 10240) { delta = Math.floor(delta/10240)*10240; start = Math.floor(start/10240)*10240; } //to lowest 10 kiB/s
else if (delta > 1024) { delta = Math.floor(delta/1024)*1024; start = Math.floor(start/1024)*1024; } //to lowest 1 kiB/s
else { delta = Math.floor(delta/102.4)*102.4; start = Math.floor(start/102.4)*102.4; } //to lowest 0.1 kiB/s
}
else { //unit: miB/s
if (delta > 104857600) { delta = Math.floor(delta/104857600)*104857600; start = Math.floor(start/104857600)*104857600; } //to lowest 100 miB/s
else if (delta > 10485760) { delta = Math.floor(delta/10485760)*10485760; start = Math.floor(start/10485760)*10485760; } //to lowest 10 miB/s
else if (delta > 1048576) { delta = Math.floor(delta/1048576)*1048576; start = Math.floor(start/1048576)*1048576; } //to lowest 1 miB/s
else { delta = Math.floor(delta/104857.6)*104857.6; start = Math.floor(start/104857.6)*104857.6; } //to lowest 0.1 miB/s
}
var out = [];
for (var i = start; i <= axis.max; i += delta) {
out.push(i);
}
return out;
}
/**
* Flot axis formatter for bandwidth
* @param val the valuea
* @param axis the axis
*/
function flotFormatBandwidthAxis(val,axis) {
if (val < 0) { var sign = '-'; }
else { var sign = ''; }
val = Math.abs(val);
if (val < 1024) { return sign+Math.round(val)+' bytes/s'; } // 0 bytes/s through 1023 bytes/s
if (val < 10235) { return sign+(val/1024).toFixed(2)+' kiB/s'; } // 1.00 kiB/s through 9.99 kiB/s
if (val < 102449) { return sign+(val/1024).toFixed(1)+' kiB/s'; } // 10.0 kiB/s through 99.9 kiB/s
if (val < 1048064) { return sign+Math.round(val/1024)+' kiB/s'; } // 100 kiB/s through 1023 kiB/s
if (val < 10480518) { return sign+(val/1048576).toFixed(2)+' miB/s'; } // 1.00 miB/s through 9.99 miB/s
if (val < 104805172) { return sign+(val/1048576).toFixed(1)+' miB/s'; } // 10.0 miB/s through 99.9 miB/s
return sign+Math.round(val/1048576)+' miB/s'; // 100 miB/s and up
}
/**
* Converts the statistics data into something flot understands
* @param stats the statistics.totals object
* @param cumulative cumulative mode if true
*/
function convertStatisticsToFlotFormat(stats,islive) {
var plotdata = [
{ label: 'Viewers', data: []},
{ label: 'Bandwidth (Up)', data: [], yaxis: 2},
{ label: 'Bandwidth (Down)', data: [], yaxis: 2}
];
var oldtimestamp = 0;
var i = 0, up = 0, down = 0;
for (var timestamp in stats) {
if (islive) {
i++;
up += stats[timestamp].up;
down += stats[timestamp].down;
//average over 5 seconds to prevent super spiky unreadable graph
if ((i % 5) == 0) {
plotdata[0].data.push([Number(timestamp)*1000,stats[timestamp].count]);
plotdata[1].data.push([Number(timestamp)*1000,up/5]);
plotdata[2].data.push([Number(timestamp)*1000,down/5]);
up = 0;
down = 0;
}
}
else {
var dt = timestamp - oldtimestamp;
if (stats[oldtimestamp]) {
var up = (stats[timestamp].up - stats[oldtimestamp].up)/dt;
var down = (stats[timestamp].down - stats[oldtimestamp].down)/dt;
}
else {
var up = stats[timestamp].up;
var down = stats[timestamp].down;
}
plotdata[0].data.push([Number(timestamp)*1000,stats[timestamp].count]);
plotdata[1].data.push([Number(timestamp)*1000,up]);
plotdata[2].data.push([Number(timestamp)*1000,down]);
oldtimestamp = timestamp;
}
}
for (var timestamp in stats) {
var dt = timestamp - oldtimestamp;
plotdata[0].data.push([Number(timestamp)*1000,stats[timestamp].count]);
if (stats[oldtimestamp]) {
var up = (stats[timestamp].up - stats[oldtimestamp].up)/dt;
var down = (stats[timestamp].down - stats[oldtimestamp].down)/dt;
}
else {
var up = stats[timestamp].up;
var down = stats[timestamp].down;
}
plotdata[1].data.push([Number(timestamp)*1000,up]);
plotdata[2].data.push([Number(timestamp)*1000,down]);
oldtimestamp = timestamp;
}
return plotdata;
}
/**
* Check if an URL points to a live datastream or a recorded file
* @param url the url in question
@ -136,9 +283,10 @@ function applyInput(){
//apply the inputs
$('input.isSetting,select.isSetting').each(function(){
var objpath = findObjPath($(this));
if ($(this).val() == '') {
if (($(this).val() == '') || ($(this).val() == 0)) {
eval('delete '+objpath+';');
}
else {
@ -163,6 +311,37 @@ function findObjPath($element) {
}
}
function ihAddBalloons() {
var page = settings.ih.pages[settings.currentpage];
if (!page) { return; }
//something with pageinfo
if (page.pageinfo) {
$('#page').prepend(
$('<div>').addClass('ih-balloon').addClass('pageinfo').html(page.pageinfo)
);
}
for (inputid in page.inputs) {
$('#'+inputid).parent().prepend(
$('<div>').addClass('ih-balloon').addClass('inputinfo').attr('data-for',inputid).html(page.inputs[inputid]).hide()
);
$('#'+inputid).focus(function(){
$('.ih-balloon[data-for='+$(this).attr('id')+']').show();
$('.ih-balloon.pageinfo').hide();
}).blur(function(){
$('.ih-balloon[data-for='+$(this).attr('id')+']').hide();
$('.ih-balloon.pageinfo').show();
});
}
$('#page label').each(function(){
$(this)
});
}
function ihMakeBalloon(contents,forid) {
return $('<div>').addClass('ih-balloon').attr('data-for',forid).html(contents).hide();
}
function getData(callBack,sendData,timeOut,doShield){
timeOut = timeOut | 30000;
var data = {};
@ -283,6 +462,81 @@ function getData(callBack,sendData,timeOut,doShield){
var jqxhr = $.ajax(obj);
}
function getWikiData(url,callBack) {
var wikiHost = 'http://rework.mistserver.org'; //must be changed when rework goes live
$('#message').removeClass('red').text('Connecting to the MistServer wiki..').append(
$('<br>')
).append(
$('<a>').text('Cancel request').click(function(){
jqxhr.abort();
})
);
var obj = {
'url': wikiHost+url,
'type': 'GET',
'crossDomain': true,
'data': {
'skin': 'plain'
},
'error':function(jqXHR,textStatus,errorThrown){
switch (textStatus) {
case 'timeout':
textStatus = $('<i>').text('The connection timed out. ');
break;
case 'abort':
textStatus = $('<i>').text('The connection was aborted. ');
break;
default:
textStatus = $('<i>').text(textStatus+'. ').css('text-transform','capitalize');
}
$('#message').addClass('red').text('An error occurred while attempting to communicate with the MistServer wiki:').append(
$('<br>')
).append(
textStatus
).append(
$('<a>').text('Send server request again').click(function(){
getWikiData(url,callback);
})
);
},
'success': function(returnedData){
$('#message').text('Wiki data received');
//convert to DOM elements
//returnedData = $.parseHTML(returnedData);
returnedData = $(returnedData);
//fix broken slash-links in the imported data
returnedData.find('a[href]').each(function(){
if ((this.hostname == '') || (this.hostname == undefined)) {
$(this).attr('href',wikiHost+$(this).attr('href'));
}
if (!$(this).attr('target')) {
$(this).attr('target','_blank');
}
}).find('img[src]').each(function(){
var a = $('<a>').attr('href',$(this).attr('src'));
if ((a.hostname == '') || (a.hostname == undefined)) {
$(this).attr('src',wikiHost+$(this).attr('src'));
}
});
consolelog('['+(new Date).toTimeString().split(' ')[0]+']','Received wiki data:',returnedData);
if (callBack) {
callBack(returnedData);
}
$('#message').text('Last communication with the MistServer wiki at '+formatTime((new Date).getTime()/1000));
}
};
var jqxhr = $.ajax(obj);
}
function saveAndReload(tabName){
var sendData = $.extend(true,{},settings.settings);
delete sendData.logs;
@ -345,12 +599,8 @@ function updateOverview() {
var streams = 0;
var streamsOnline = 0;
for (var index in data.statistics) {
if (data.statistics[index].curr) {
for (viewer in data.statistics[index].curr) {
viewers++;
}
}
if (data.clients && data.clients.data) {
viewers = data.clients.data.length;
}
for (var index in data.streams) {
@ -363,7 +613,9 @@ function updateOverview() {
$('#cur_streams_online').text(streamsOnline+'/'+streams+' online');
$('#cur_num_viewers').text(seperateThousands(viewers,' '));
$('#settings-config-time').text(formatDateLong(data.config.time));
});
settings.settings.statistics = data.statistics;
},{clients: {}});
}
function updateProtocols() {
getData(function(data){
@ -380,6 +632,9 @@ function updateProtocols() {
function displayProtocolSettings(theProtocol) {
var capabilities = settings.settings.capabilities.connectors[theProtocol.connector];
if (!capabilities) {
return '';
}
var settingsList = [];
for (var index in capabilities.required) {
if ((theProtocol[index]) && (theProtocol[index] != '')) {
@ -478,20 +733,31 @@ function buildProtocolParameterFields(data,required,objpath) {
return $container.html();
}
function updateStreams() {
var streamlist = [];
for (var stream in settings.settings.streams) {
streamlist.push(stream);
}
getData(function(data){
var datafields = {};
for (var index in data.clients.fields) {
datafields[data.clients.fields[index]] = index;
}
var viewers = {};
for (var index in data.clients.data) {
if (viewers[data.clients.data[index][datafields['stream']]]) {
viewers[data.clients.data[index][datafields['stream']]]++;
}
else {
viewers[data.clients.data[index][datafields['stream']]] = 1;
}
}
for (var index in data.streams) {
$('#status-of-'+index).html(formatStatus(data.streams[index]))
$('#viewers-of-'+index).text(seperateThousands(viewers[index],' '));
}
for (var index in data.statistics) {
var viewers = 0;
if (data.statistics[index].curr) {
for (var jndex in data.statistics[index].curr) {
viewers++;
}
}
$('#viewers-of-'+index).text(seperateThousands(viewers,' '));
}
});
settings.settings.statistics = data.statistics;
},{clients:{}});
}
function filterTable() {
var displayRecorded = $('#stream-filter-recorded').is(':checked');
@ -886,12 +1152,19 @@ function conversionSelectInput(theFiles) {
applyInput();
var extension = settings.settings.conversion.convert._new_.output.split('.');
if (extension[extension.length-1] != 'dtsc') {
extension.push('dtsc');
settings.settings.conversion.convert._new_.output = extension.join('.');
}
settings.settings.conversion.convert._new_.output = settings.settings.conversion.convert._new_.outputdir.replace(/\/$/,'')+'/'+settings.settings.conversion.convert._new_.output;
delete settings.settings.conversion.convert._new_.outputdir;
if ((settings.settings.conversion.convert._new_.video) && (settings.settings.conversion.convert._new_.video.fps)) {
settings.settings.conversion.convert._new_.fpks = Math.floor(settings.settings.conversion.convert._new_.fps * 1000);
}
settings.settings.conversion.convert['c_'+(new Date).getTime()] = settings.settings.conversion.convert._new_;
delete settings.settings.conversion.convert._new_;
saveAndReload('conversion');
@ -1019,10 +1292,85 @@ function updateServerstats() {
},{capabilities:true});
}
function buildstreamembed(streamName,embedbase) {
$('#liststreams .button.current').removeClass('current')
$('#liststreams .button').filter(function(){
return $(this).text() == streamName;
}).addClass('current');
$('#subpage').append(
$('<div>').addClass('input_container').html(
$('<label>').text('The info embed URL is:').append(
$('<input>').attr('type','text').attr('readonly','readonly').val(embedbase+'info_'+streamName+'.js')
)
).append(
$('<label>').text('The embed URL is:').append(
$('<input>').attr('type','text').attr('readonly','readonly').val(embedbase+'embed_'+streamName+'.js')
)
).append(
$('<label>').text('The embed code is:').css('overflow','hidden').append(
$('<textarea>').val('<div>\n <script src="'+embedbase+'embed_'+streamName+'.js"></' + 'script>\n</div>')
)
)
).append(
$('<span>').attr('id','listprotocols').text('Loading..')
).append(
$('<p>').text('Preview:')
).append(
$('<div>').attr('id','preview-container')
);
// jQuery doesn't work -> use DOM magic
var script = document.createElement('script');
script.src = embedbase+'embed_'+streamName+'.js';
script.onload = function(){
var priority = mistvideo[streamName].source;
if (priority.length > 0) {
priority.sort(function(a,b){
return b.priority - a.priority;
});
var $table = $('<table>').html(
$('<tr>').html(
$('<th>').text('URL')
).append(
$('<th>').text('Type')
).append(
$('<th>').text('Priority')
)
);
for (var i in priority) {
$table.append(
$('<tr>').html(
$('<td>').text(priority[i].url)
).append(
$('<td>').text(priority[i].type)
).append(
$('<td>').addClass('align-center').text(priority[i].priority)
)
);
}
$('#listprotocols').html($table);
}
else {
$('#listprotocols').html('No data in info embed file.');
}
}
document.getElementById('preview-container').appendChild( script );
}
$(function(){
$('#menu div.button').click(function(){
if ((settings.settings.LTS != 1) && ($(this).hasClass('LTS-only'))) { return; }
$('#logo > a').click(function(){
if ($.isEmptyObject(settings.settings)) {
showTab('login')
}
else {
showTab('overview');
}
});
$('#menu div.button').click(function(e){
//if ((settings.settings.LTS != 1) && ($(this).hasClass('LTS-only'))) { return; }
showTab($(this).text().toLowerCase());
e.stopPropagation();
})
$('body').on('keydown',function(e){
switch (e.which) {
@ -1101,6 +1449,41 @@ $(function(){
$(this).val(v);
this.setSelectionRange(curpos,curpos);
});
$('.expandbutton').click(function(){
$(this).toggleClass('active');
});
$('#ih-button').click(function(){
if (ih) {
$('.ih-balloon').remove();
}
else {
getWikiData('/wiki/Integrated_Help',function(data){
settings.ih = {
raw: data.find('#mw-content-text').contents(),
pages: {}
}
settings.ih.raw.filter('.page[data-pagename]').each(function(){
var pagename = $(this).attr('data-pagename').replace(' ','_');
settings.ih.pages[pagename] = {
raw: $(this).contents(),
pageinfo: $(this).find('.page-description').html(),
inputs: {}
}
$(this).children('.input-description[data-inputid]').each(function(){
settings.ih.pages[pagename].inputs[$(this).attr('data-inputid')] = $(this).html();
});
});
consolelog('New integrated help data:',settings.ih);
ihAddBalloons();
});
}
ih = !ih;
$(this).toggleClass('active');
});
});
$(window).on('hashchange', function(e) {

View file

@ -6,6 +6,7 @@ var defaults = {
};
function showTab(tabName,streamName) {
settings.currentpage = tabName.replace(' ','_');
ignoreHashChange = true;
location.hash = location.hash.split('&')[0]+'&'+tabName+(streamName ? '@'+streamName : '');
@ -14,9 +15,10 @@ function showTab(tabName,streamName) {
$('#menu .button').removeClass('current').filter(function(i){
return $(this).text().toLowerCase() == tabName;
}).addClass('current');
}).addClass('current').parents('.expandbutton').addClass('active');
$('#page').html('');
$('#tooltip').remove();
clearInterval(theInterval);
$('#menu').css('visibility', 'visible');
@ -121,6 +123,10 @@ function showTab(tabName,streamName) {
saveAndReload('overview');
})
).append(
$('<button>').text('Cancel').addClass('escape-to-cancel').click(function(){
showTab('login');
})
)
);
break;
@ -434,8 +440,12 @@ function showTab(tabName,streamName) {
$('<td>').attr('id','status-of-'+index).html(formatStatus(theStream))
).append(
$('<td>').html(
$('<button>').text('Embed').click(function(){
showTab('embed',$(this).parent().parent().data('stream'))
$('<button>').text('Preview').click(function(){
showTab('preview',$(this).parent().parent().data('stream'))
})
).append(
$('<button>').text('Info').click(function(){
showTab('streaminfo',$(this).parent().parent().data('stream'))
})
)
).append(
@ -497,7 +507,7 @@ function showTab(tabName,streamName) {
$('<label>').text('Buffer time:').addClass('live-only').attr('for','settings-streams-'+streamName+'-DVR').append(
$('<span>').addClass('unit').text('[ms]')
).append(
$('<input>').attr('type','text').attr('id','settings-streams-'+streamName+'-DVR').attr('placeholder','2 keyframes').addClass('isSetting').addClass('').addClass('validate-positive-integer')
$('<input>').attr('type','text').attr('id','settings-streams-'+streamName+'-DVR').attr('placeholder','30000').addClass('isSetting').addClass('').addClass('validate-positive-integer')
)
).append(
$('<label>').text('Record to:').addClass('live-only').addClass('LTS-only').attr('for','settings-streams-'+streamName+'-record').attr('title','The path to the file to record to. Leave this field blank if you do not wish to record to file.').append(
@ -516,7 +526,7 @@ function showTab(tabName,streamName) {
$('<p>').text('Encrypt this stream')
).append(
$('<div>').addClass('description').text(
'To enable encryption, the Licene Acquisition URL must be entered, as well as either the content key or the key ID and seed.'
'To enable encryption, the Licence Acquisition URL must be entered, as well as either the content key or the key ID and seed.'
)
).append(
$('<label>').text('Licence Acquisition URL:').attr('for','settings-streams-'+streamName+'-la_url').append(
@ -578,7 +588,131 @@ function showTab(tabName,streamName) {
})
break;
case 'embed':
case 'streaminfo':
var meta = settings.settings.streams[streamName].meta;
if (!meta) {
$('#page').html('No info available for stream "'+streamName+'".');
}
else {
$meta = $('<table>').css('width','auto');
if (meta.live) {
$meta.html(
$('<tr>').html(
$('<td>').text('Type:')
).append(
$('<td>').text('Live')
)
);
}
else {
$meta.html(
$('<tr>').html(
$('<td>').text('Type:')
).append(
$('<td>').text('Pre-recorded (VoD)')
)
);
}
for (var index in meta.tracks) {
var track = meta.tracks[index];
if (track.type == '') { continue; }
var $table = $('<table>').html(
$('<tr>').html(
$('<td>').text('Type:')
).append(
$('<td>').text(capFirstChar(track.type))
)
).append(
$('<tr>').html(
$('<td>').text('Codec:')
).append(
$('<td>').text(track.codec)
)
).append(
$('<tr>').html(
$('<td>').text('Duration:')
).append(
$('<td>').html(
formatDuration(track.lastms-track.firstms)+'<br>(from '+formatDuration(track.firstms)+' to '+formatDuration(track.lastms)+')'
)
)
).append(
$('<tr>').html(
$('<td>').text('Average bitrate:')
).append(
$('<td>').text(Math.round(track.bps/1024)+' KiB/s')
)
);
if (track.height) {
$table.append(
$('<tr>').html(
$('<td>').text('Size:')
).append(
$('<td>').text(track.width+'x'+track.height+' px')
)
);
}
if (track.fpks) {
$table.append(
$('<tr>').html(
$('<td>').text('Framerate:')
).append(
$('<td>').text(track.fpks/1000+' fps')
)
);
}
if (track.channels) {
$table.append(
$('<tr>').html(
$('<td>').text('Channels:')
).append(
$('<td>').text(track.channels)
)
);
}
if (track.rate) {
$table.append(
$('<tr>').html(
$('<td>').text('Samplerate:')
).append(
$('<td>').text(seperateThousands(track.rate,' ')+' Hz')
)
);
}
$meta.append(
$('<tr>').html(
$('<td>').text(capFirstChar(index)+':')
).append(
$('<td>').html(
$table
)
)
);
}
$('#page').html(
$('<p>').text('Detailed information about stream "'+streamName+'"')
).append(
$('<div>').css({'width':'100%','display':'table','table-layout':'fixed','min-height':'300px'}).html(
$('<div>').css('display','table-row').html(
$('<div>').attr('id','info-stream-meta').css({'display':'table-cell','max-width':'50%','overflow':'auto'}).html(
$meta
)
).append(
$('<div>').attr('id','info-stream-statistics').css({'display':'table-cell','text-align':'center','min-height':'200px'})
)
)
);
}
$('#page').append(
$('<button>').text('Back').addClass('escape-to-cancel').click(function(){
showTab('streams');
})
);
break;
case 'preview':
var httpConnector = false;
for (var index in settings.settings.config.protocols) {
if ((settings.settings.config.protocols[index].connector == 'HTTP') || (settings.settings.config.protocols[index].connector == 'HTTP.exe')) {
@ -586,37 +720,29 @@ function showTab(tabName,streamName) {
}
}
if (httpConnector) {
var embedbase = 'http://'+parseURL(settings.server).host+':'+(httpConnector.port ? httpConnector.port : 8080)+'/';
$('#page').html(
$('<div>').addClass('input_container').html(
$('<p>').text('Embed info for stream "'+streamName+'"')
).append(
$('<label>').text('The info embed URL is:').append(
$('<input>').attr('type','text').attr('readonly','readonly').val(embedbase+'info_'+streamName+'.js')
$('<div>').addClass('table').html(
$('<div>').addClass('row').html(
$('<div>').addClass('cell').attr('id','liststreams').addClass('menu')
).append(
$('<div>').addClass('cell').attr('id','subpage').css('padding-left','1em')
)
).append(
$('<label>').text('The embed URL is:').append(
$('<input>').attr('type','text').attr('readonly','readonly').val(embedbase+'embed_'+streamName+'.js')
)
).append(
$('<label>').text('The embed code is:').css('overflow','hidden').append(
$('<textarea>').val('<div>\n <script src="'+embedbase+'embed_'+streamName+'.js"></' + 'script>\n</div>')
)
).append(
$('<button>').text('Back').addClass('escape-to-cancel').click(function(){
showTab('streams');
})
)
).append(
$('<p>').text('Preview:')
).append(
$('<div>').attr('id','preview-container')
);
var embedbase = 'http://'+parseURL(settings.server).host+':'+(httpConnector.port ? httpConnector.port : 8080)+'/';
// jQuery doesn't work -> use DOM magic
var script = document.createElement('script');
script.src = embedbase+'embed_'+streamName+'.js';
document.getElementById('preview-container').appendChild( script );
for (var s in settings.settings.streams) {
if (!streamName) {
streamName = s;
}
$('#liststreams').append(
$('<div>').addClass('button').text(settings.settings.streams[s].name).click(function(){
buildstreamembed($(this).text());
})
);
}
buildstreamembed(streamName,embedbase);
}
else {
$('#page').html(
@ -627,29 +753,31 @@ function showTab(tabName,streamName) {
case 'limits':
var $tbody = $('<tbody>');
$('#page').html(
$('<div>').addClass('description').text('This is an overview of the limits that have been configured on MistServer.')
).append(
$('<table>').html(
$('<thead>').html(
$('<tr>').html(
$('<th>').text('Applies to')
).append(
$('<th>').text('Type')
).append(
$('<th>').text('Name')
).append(
$('<th>').text('Value')
).append(
$('<th>')
$('<div>').addClass('LTS-only').html(
$('<div>').addClass('description').text('This is an overview of the limits that have been configured on MistServer.')
).append(
$('<table>').html(
$('<thead>').html(
$('<tr>').html(
$('<th>').text('Applies to')
).append(
$('<th>').text('Type')
).append(
$('<th>').text('Name')
).append(
$('<th>').text('Value')
).append(
$('<th>')
)
)
).append(
$tbody
)
).append(
$tbody
$('<button>').text('New').click(function(){
showTab('edit limit','_new_');
})
)
).append(
$('<button>').text('New').click(function(){
showTab('edit limit','_new_');
})
);
for (var index in settings.settings.config.limits) {
@ -1041,6 +1169,551 @@ function showTab(tabName,streamName) {
}
$('#logs-refresh-every').val(defaults.logRefreshing[1]);
break;
case 'statistics':
var graphs = {};
var plot;
$('#page').html(
$('<div>').addClass('description').text('Here, you can select all kinds of data, and view them in a graph.')
).append(
$('<div>').addClass('input_container').html(
$('<p>').text('Select the data to display')
).append(
$('<label>').text('Add to graph:').append(
$('<select>').attr('id','graphid').html(
$('<option>').text('New graph').val('new')
).change(function(){
if ($(this).val() == 'new') {
$('#graphtype').removeAttr('disabled');
}
else {
$('#graphtype').attr('disabled','disabled');
//set to correct type
}
})
)
).append(
$('<label>').text('Graph x-axis type:').append(
$('<select>').attr('id','graphtype').html(
$('<option>').text('Time line').val('time')
).append(
$('<option>').text('Map').val('coords')
).change(function(){
$('#dataset option').hide();
$('#dataset option.axis_'+$(this).val()).show();
$('#dataset').val( $('#dataset option.axis_'+$(this).val()).first().val());
})
)
).append(
$('<label>').text('Select data set:').append(
$('<select>').attr('id','dataset').html(
$('<option>').text('Viewers').val('clients').addClass('axis_time')
).append(
$('<option>').text('Bandwidth (up)').val('upbps').addClass('axis_time')
).append(
$('<option>').text('Bandwidth (down)').val('downbps').addClass('axis_time')
).append(
$('<option>').text('% CPU').val('cpuload').addClass('axis_time')
).append(
$('<option>').text('Memory load').val('memload').addClass('axis_time')
).append(
$('<option>').text('Viewer location').val('coords').addClass('axis_coords')
).change(function(){
switch ($(this).val()) {
case 'clients':
case 'upbps':
case 'downbps':
$('#dataset-details .replace-dataset').text('amount of viewers')
$('#dataset-details').show();
break;
default:
$('#dataset-details').hide();
}
})
)
).append(
$('<div>').attr('id','dataset-details').addClass('checklist').css({
'padding':'0.5em 0 0 40%',
'font-size':'0.9em'
}).html('Show <span class=replace-dataset></span> for:').append(
$('<label>').text('The total').prepend(
$('<input>').attr('type','radio').attr('name','cumutype').attr('checked','checked').val('all')
)
).append(
$('<label>').text('The stream ').append(
$('<select>').addClass('stream cumuval')
).prepend(
$('<input>').attr('type','radio').attr('name','cumutype').val('stream')
)
).append(
$('<label>').text('The protocol ').append(
$('<select>').addClass('protocol cumuval')
).prepend(
$('<input>').attr('type','radio').attr('name','cumutype').val('protocol')
)
)
).append(
$('<button>').text('Add data set').click(function(){
//the graph
if ($('#graphid').val() == 'new') {
var graph = {};
graph.id = $('#graphid').val();
graph.type = $('#graphtype').val();
graph.id = 'graph_'+($('#graphcontainer .graph').length+1);
graph.datasets = [];
graphs[graph.id] = graph;
$('#graphcontainer').append(
$('<div>').attr('id',graph.id).addClass('graph-item').html(
$('<div>').addClass('legend')
).append(
$('<div>').addClass('graph')
)
);
$('#graphid').append(
$('<option>').text(graph.id)
).val(graph.id).trigger('change');
}
else {
var graph = graphs[$('#graphid').val()];
}
//the dataset itself
var d = {
display: true,
type: $('#dataset').val(),
label: '',
yaxistype: 'amount',
data: [],
lines: { show: true },
points: { show: false }
};
switch (d.type) {
case 'cpuload':
d.label = 'CPU load';
d.yaxistype = 'percentage';
break;
case 'memload':
d.label = 'Memory load';
d.yaxistype = 'percentage';
break;
case 'upbps':
case 'downbps':
case 'clients':
d.cumutype = $('#dataset-details input[name=cumutype]:checked').val();
d.yaxistype = 'bytespersec';
if (d.cumutype == 'all') {
switch (d.type) {
case 'clients':
d.label = 'Total viewers';
d.yaxistype = 'amount';
break;
case 'upbps':
d.label = 'Total bandwidth (up)';
break;
case 'downbps':
d.label = 'Total bandwidth (down)';
break;
}
}
else {
var which = $('#dataset-details.cumuval.'+d.cumutype).val();
if (d.cumutype == 'stream') {
d.stream = which;
}
else if (d.cumutype == 'protocol') {
d.protocol = which;
}
switch (d.type) {
case 'clients':
d.label = 'Viewers ('+d.stream+')';
d.yaxistype = 'amount';
break;
case 'upbps':
d.label = 'Bandwidth (up) ('+d.stream+')';
break;
case 'downbps':
d.label = 'Bandwidth (down) ('+d.stream+')';
break;
}
}
break;
}
graph.datasets.push(d);
getPlotData();
})
)/*.append(
$('<p>').text('Switch data display type').css('clear','both')
).append(
$('<label>').text('Show data in a:').append(
$('<select>').html(
$('<option>').text('graph')
).append(
$('<option>').text('table')
)
)
)*/
).append(
$('<div>').attr('id','graphcontainer')
);
for (var i in settings.settings.streams) {
$('#dataset-details .cumuval.stream').append(
$('<option>').text(settings.settings.streams[i].name).val(i)
);
}
for (var i in settings.settings.config.protocols) {
$('#dataset-details .cumuval.protocol').append(
$('<option>').text(settings.settings.config.protocols[i].connector)
);
}
$('#graphtype').trigger('change');
var lastitem = null;
var $tooltip = $('<div>').attr('id','tooltip');
$('body').append($tooltip);
$('.graph').live('plothover',function(e,pos,item){
if (item) {
var pos;
if (item.pageX > ($(window).width() / 2)) {
pos.left = 'auto';
pos.right = $(window).width() - item.pageX + 8+'px';
}
else {
pos.left = item.pageX + 8+'px';
pos.right = 'auto';
}
if (item.pageY > ($(window).height() / 2)) {
pos.top = 'auto';
pos.bottom = $(window).height() - item.pageY + 8+'px';
}
else {
pos.top = item.pageY + 8+'px';
pos.bottom = 'auto';
}
$tooltip.css({
'left': pos.left,
'top': pos.top,
'right': pos.right,
'bottom': pos.bottom
}).html(
$('<p>').text(item.series.label).prepend(
$('<div>').css({
'background-color': item.series.color,
'width': '20px',
'height': '20px',
'display': 'inline-block',
'margin': '0 0.5em'
})
)
).append(
$('<table>').html(
$('<tr>').html(
$('<td>').text('Time:')
).append(
$('<td>').text(item.series.xaxis.tickFormatter(item.datapoint[0],item.series.xaxis))
)
).append(
$('<tr>').html(
$('<td>').text(item.series.label+':')
).append(
$('<td>').text(item.series.yaxis.tickFormatter(item.datapoint[1],item.series.yaxis))
)
)
).fadeIn();
}
else {
$('#tooltip').hide();
}
});
theInterval = setInterval(function(){
getPlotData();
},10000);
function getPlotData() {
getData(function(data){
for (var j in graphs) {
for (var i in graphs[j].datasets) {
graphs[j].datasets[i] = findDataset(graphs[j].datasets[i],data);
}
drawGraph(graphs[j]);
}
},{capabilities:true,totals:{}});
}
function findDataset(dataobj,sourcedata) {
var now = sourcedata.config.time;
switch (dataobj.type) {
case 'cpuload':
//remove any data older than 10 minutes
var removebefore = false;
for (var i in dataobj.data) {
if (dataobj.data[i][0] < (now-600)*1000) {
removebefore = Number(i)+1;
}
}
if (removebefore !== false) {
dataobj.data.splice(0,removebefore);
}
dataobj.data.push([now*1000,sourcedata.capabilities.load.one]);
break;
case 'memload':
//remove any data older than 10 minutes
var removebefore = false;
for (var i in dataobj.data) {
if (dataobj.data[i][0] < (now-600)*1000) {
removebefore = Number(i)+1;
}
}
if (removebefore !== false) {
dataobj.data.splice(0,removebefore);
}
dataobj.data.push([now*1000,sourcedata.capabilities.load.memory]);
break;
case 'upbps':
case 'downbps':
case 'clients':
//todo: depending on the stream..
if (!sourcedata.totals || !sourcedata.totals.data) {
dataobj.data.push([(now-600)*1000,0]);
dataobj.data.push([now*1000,0]);
}
else {
var fields = {};
for (var index in sourcedata.totals.fields) {
fields[sourcedata.totals.fields[index]] = index;
}
var time = sourcedata.totals.start;
dataobj.data = [];
if (time > now-590) {
//prepend data with 0
dataobj.data.push([(now-600)*1000,0]);
dataobj.data.push([time*1000-1,0]);
}
var index = 0;
dataobj.data.push([[time*1000,sourcedata.totals.data[index][fields[dataobj.type]]]]);
for (var i in sourcedata.totals.interval) {
if ((i % 2) == 1) {
//fill gaps with 0
time += sourcedata.totals.interval[i][1];
dataobj.data.push([time*1000,0]);
}
else {
for (var j = 0; j < sourcedata.totals.interval[i][0]; j++) {
time += sourcedata.totals.interval[i][1];
index++;
dataobj.data.push([time*1000,sourcedata.totals.data[index][fields[dataobj.type]]]);
}
if (i < sourcedata.totals.interval.length-1) {
dataobj.data.push([time*1000+1,0]);
}
}
}
if (now > time + 10) {
//append data with 0
dataobj.data.push([time*1000+1,0]);
dataobj.data.push([now*1000,0]);
}
}
break;
}
return dataobj;
}
function drawGraph(graph){
var datasets = graph.datasets;
if (datasets.length < 1) {
$('#'+graph.id).children('.graph,.legend').html('');
return;
}
var yaxes = [];
var yaxesTemplates = {
percentage: {
name: 'percentage',
color: 'black',
display: false,
tickColor: 0,
tickDecimals: 0,
tickFormatter: function(val,axis){
return val.toFixed(axis.tickDecimals) + '%';
},
tickLength: 0,
min: 0
},
amount: {
name: 'amount',
color: 'black',
display: false,
tickColor: 0,
tickDecimals: 0,
tickFormatter: function(val,axis){
return seperateThousands(val.toFixed(axis.tickDecimals),' ');
},
tickLength: 0,
min: 0
},
bytespersec: {
name: 'bytespersec',
color: 'black',
display: false,
tickColor: 0,
tickDecimals: 1,
tickFormatter: function(val,axis){
var suffix = ['bytes','KiB','MiB','GiB','TiB','PiB'];
if (val == 0) {
val = val+' '+suffix[0];
}
else {
var exponent = Math.floor(Math.log(Math.abs(val)) / Math.log(1024));
if (exponent < 0) {
val = val.toFixed(axis.tickDecimals)+' '+suffix[0];
}
else {
val = Math.round(val / Math.pow(1024,exponent) * Math.pow(10,axis.tickDecimals)) / Math.pow(10,axis.tickDecimals) +' '+suffix[exponent];
}
}
return val + '/s';
},
tickLength: 0,
ticks: function(axis,a,b,c,d){
//taken from flot source code (function setupTickGeneration),
//modified to think in multiples of 1024 by Carina van der Meer for DDVTECH
// heuristic based on the model a*sqrt(x) fitted to
// some data points that seemed reasonable
var noTicks = 0.3 * Math.sqrt($('.graph').first().height());
var delta = (axis.max - axis.min) / noTicks,
exponent = Math.floor(Math.log(Math.abs(delta)) / Math.log(1024)),
correcteddelta = delta / Math.pow(1024,exponent),
dec = -Math.floor(Math.log(correcteddelta) / Math.LN10),
maxDec = axis.tickDecimals;
if (maxDec != null && dec > maxDec) {
dec = maxDec;
}
var magn = Math.pow(10, -dec),
norm = correcteddelta / magn, // norm is between 1.0 and 10.0
size;
if (norm < 1.5) {
size = 1;
} else if (norm < 3) {
size = 2;
// special case for 2.5, requires an extra decimal
if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
size = 2.5;
++dec;
}
} else if (norm < 7.5) {
size = 5;
} else {
size = 10;
}
size *= magn;
size = size * Math.pow(1024,exponent);
if (axis.minTickSize != null && size < axis.minTickSize) {
size = axis.minTickSize;
}
axis.delta = delta;
axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
axis.tickSize = size;
var ticks = [],
start = axis.tickSize * Math.floor(axis.min / axis.tickSize),
i = 0,
v = Number.NaN,
prev;
do {
prev = v;
v = start + i * axis.tickSize;
ticks.push(v);
++i;
} while (v < axis.max && v != prev);
return ticks;
},
min: 0
}
};
var xaxistemplates = {
time: {
name: 'time',
mode: 'time',
timezone: 'browser',
ticks: 5
}
}
var plotsets = [];
for (var i in datasets) {
if (datasets[i].display) {
if (yaxesTemplates[datasets[i].yaxistype].display === false) {
yaxes.push(yaxesTemplates[datasets[i].yaxistype]);
yaxesTemplates[datasets[i].yaxistype].display = yaxes.length;
}
datasets[i].yaxis = yaxesTemplates[datasets[i].yaxistype].display;
datasets[i].color = Number(i);
plotsets.push(datasets[i]);
}
}
if (yaxes[0]) { yaxes[0].color = 0; }
plot = $.plot(
$('#'+graph.id+' .graph'),
plotsets,
{
legend: {show: false},
xaxis: xaxistemplates[graph.type],
yaxes: yaxes,
grid: {
hoverable: true,
borderWidth: {top: 0, right: 0, bottom: 1, left: 1},
color: 'black',
backgroundColor: {colors: ['#fff','#ededed']}
}
}
);
$('#'+graph.id+' .legend').html(
$('<div>').addClass('legend-list').addClass('checklist')
);
var plotdata = plot.getOptions();
for (var i in datasets) {
var $checkbox = $('<input>').attr('type','checkbox').data('dataset-index',i).click(function(){
if ($(this).is(':checked')) {
datasets[$(this).data('dataset-index')].display = true;
}
else {
datasets[$(this).data('dataset-index')].display = false;
}
drawGraph($(this).parents('.graph-item'));
});
if (datasets[i].display) {
$checkbox.attr('checked','checked');
}
$('#'+graph.id+' .legend-list').append(
$('<label>').html(
$checkbox
).append(
$('<div>').addClass('series-color').css('background-color',plotdata.colors[datasets[i].color % plotdata.colors.length])
).append(
datasets[i].label
)
);
}
if (datasets.length > 0) {
$('#'+graph.id+' .legend').append(
$('<button>').text('Clear all').click(function(){
var graph = graphs[$(this).parents('.graph-item').attr('id')];
graph.datasets = [];
drawGraph(graph);
}).css({'float':'none'})
);
}
}
break;
case 'server stats':
var $cont = $('<div>').addClass('input_container');
@ -1094,6 +1767,55 @@ function showTab(tabName,streamName) {
$('#page').html($cont);
break;
case 'email for help':
var config = $.extend({},settings.settings);
delete config.statistics;
config = JSON.stringify(config);
$('#page').html(
$('<div>').addClass('description').html(
'You can use this form to email MistServer support if you\'re having difficulties.<br>'
).append(
'A copy of your server config file will automatically be included.'
)
).append(
$('<div>').addClass('input_container').html(
$('<form>').html(
$('<label>').text('Your name:').append(
$('<input>').attr('type','text').attr('name','name')
)
).append(
$('<input>').attr('type','hidden').attr('name','company').val('-')
).append(
$('<label>').text('Your email address:').append(
$('<input>').attr('type','email').attr('name','email')
)
).append(
$('<input>').attr('type','hidden').attr('name','subject').val('Integrated Help')
).append(
$('<label>').text('Your message:').append(
$('<textarea>').attr('name','message').height('20em')
)
).append(
$('<label>').text('Your config file:').append(
$('<textarea>').attr('name','configfile').attr('readonly','readonly').css({'height':'20em','font-size':'0.7em'}).val(config)
)
).append(
$('<button>').text('Send').click(function(e){
var data = $(this).parents('form').serialize();
$.ajax({
type: 'POST',
url: 'http://mistserver.org/contact_us?skin=plain',
data: data,
success: function(d) {
$('#page').html(d);
}
});
e.preventDefault();
})
)
)
);
break;
case 'disconnect':
showTab('login');
$('#connection').addClass('red').removeClass('green').text('Disconnected');
@ -1110,17 +1832,32 @@ function showTab(tabName,streamName) {
if ((settings.credentials.authstring) && (!settings.settings.LTS)) {
$('.LTS-only input').add('.LTS-only select').add('.LTS-only button').attr('disabled','disabled');
$('.LTS-only, .LTS-only p, .LTS-only label, .LTS-only button ').css('color','#b4b4b4');
//$('.LTS-only, .LTS-only p, .LTS-only label, .LTS-only button').css('color','#b4b4b4');
$('.LTS-only, .LTS-only > *').filter(':not(.LTSstuff_done)').each(function(){
var t = [];
if ($(this).attr('title')) {
t.push($(this).attr('title'));
}
t.push('This is feature is only available in the LTS version.');
t.push('This feature is only available in the LTS version.');
$(this).attr('title',t.join(' ')).addClass('LTSstuff_done');
});
$('#page .LTS-only').prepend(
$('<a>').text('Upgrade to LTS').attr('target','_blank').attr('href','http://mistserver.org/products/MistServer LTS').addClass('fakebutton')
);
$('.linktoReleaseNotes.notedited').each(function(){
$(this).attr('href',$(this).attr('href')+'/'+settings.settings.config.version.split('-')[0]).removeClass('.notedited');
});
}
else if (settings.settings.LTS) {
$('.LTS-only').removeClass('LTS-only');
$('.linktoTnC.notLTSlink').attr('href','http://mistserver.org/wiki/MistServerLTS_license').removeClass('notLTSlink');
$('.linktoReleaseNotes.notedited').each(function(){
$(this).attr('href',$(this).attr('href')+'/'+settings.settings.config.version.split('-')[0]+'LTS').removeClass('.notedited');
});
}
if (ih) {
ihAddBalloons();
}
}

View file

@ -0,0 +1 @@
(function($){var options={crosshair:{mode:null,color:"rgba(170, 0, 0, 0.80)",lineWidth:1}};function init(plot){var crosshair={x:-1,y:-1,locked:false};plot.setCrosshair=function setCrosshair(pos){if(!pos)crosshair.x=-1;else{var o=plot.p2c(pos);crosshair.x=Math.max(0,Math.min(o.left,plot.width()));crosshair.y=Math.max(0,Math.min(o.top,plot.height()))}plot.triggerRedrawOverlay()};plot.clearCrosshair=plot.setCrosshair;plot.lockCrosshair=function lockCrosshair(pos){if(pos)plot.setCrosshair(pos);crosshair.locked=true};plot.unlockCrosshair=function unlockCrosshair(){crosshair.locked=false};function onMouseOut(e){if(crosshair.locked)return;if(crosshair.x!=-1){crosshair.x=-1;plot.triggerRedrawOverlay()}}function onMouseMove(e){if(crosshair.locked)return;if(plot.getSelection&&plot.getSelection()){crosshair.x=-1;return}var offset=plot.offset();crosshair.x=Math.max(0,Math.min(e.pageX-offset.left,plot.width()));crosshair.y=Math.max(0,Math.min(e.pageY-offset.top,plot.height()));plot.triggerRedrawOverlay()}plot.hooks.bindEvents.push(function(plot,eventHolder){if(!plot.getOptions().crosshair.mode)return;eventHolder.mouseout(onMouseOut);eventHolder.mousemove(onMouseMove)});plot.hooks.drawOverlay.push(function(plot,ctx){var c=plot.getOptions().crosshair;if(!c.mode)return;var plotOffset=plot.getPlotOffset();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);if(crosshair.x!=-1){var adj=plot.getOptions().crosshair.lineWidth%2===0?0:.5;ctx.strokeStyle=c.color;ctx.lineWidth=c.lineWidth;ctx.lineJoin="round";ctx.beginPath();if(c.mode.indexOf("x")!=-1){var drawX=Math.round(crosshair.x)+adj;ctx.moveTo(drawX,0);ctx.lineTo(drawX,plot.height())}if(c.mode.indexOf("y")!=-1){var drawY=Math.round(crosshair.y)+adj;ctx.moveTo(0,drawY);ctx.lineTo(plot.width(),drawY)}ctx.stroke()}ctx.restore()});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mouseout",onMouseOut);eventHolder.unbind("mousemove",onMouseMove)})}$.plot.plugins.push({init:init,options:options,name:"crosshair",version:"1.0"})})(jQuery);

2
lsp/plugins/jquery.flot.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
lsp/plugins/jquery.flot.time.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
lsp/plugins/jquery.js vendored Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long