$(function(){ showTab('login'); }); var settings = { credentials:{}, settings: {} }; var debug = false; function consolelog() { if (debug) { console.log.apply(console,arguments); } } var ih = false; function confirmDelete(question){ return confirm(question); } /** * Add thousand seperator to a number * @param number the number to format * @param seperator the seperator to use */ function seperateThousands(number,seperator){ if (isNaN(Number(number))) return number; number = number.toString().split('.'); var regex = /(\d+)(\d{3})/; while (regex.test(number[0])) { number[0] = number[0].replace(regex,'$1'+seperator+'$2'); } return number.join('.'); } /** * Format a date to mmm d yyyy, hh:mm:ss format * @param date the date to format (timestamp in seconds) */ function formatDateLong(date){ var d = new Date(date * 1000); return [ nameMonth(d.getMonth()), d.getDate(), d.getFullYear() ].join(' ') + ', ' + formatTime(date); } function nameMonth(monthNum){ months = Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'); return months[monthNum]; } function formatTime(date){ var d = new Date(date * 1000); return [ ('00' + d.getHours()).slice(-2), ('00' + d.getMinutes()).slice(-2), ('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 */ function isLive(url){ var protocol = /([a-zA-Z]+):\/\//.exec(url); if ((protocol === null) || ((protocol[1]) && (protocol[1] === 'file'))) { return false; } else { return true; } } /** * parses an url and returns the parts of it. * @return object containing the parts of the URL: protocol, host and port. */ function parseURL(url) { var pattern = /(https?)\:\/\/([^:\/]+)\:(\d+)?/i; var retobj = {protocol: '', host: '', port: ''}; var results = url.match(pattern); if(results != null) { retobj.protocol = results[1]; retobj.host = results[2]; retobj.port = results[3]; } return retobj; } /** * Return html describing the status of the stream or protocol * @param theStream the stream or protocol object in question */ function formatStatus(theStream) { if (theStream.online == undefined) { return $('<span>').text('Unknown, checking..'); } if (theStream.error == undefined) { switch (theStream.online) { case -1: return $('<span>').text('Unknown, checking..'); break; case 0: return $('<span>').addClass('red').text('Unavailable'); break; case 1: return $('<span>').addClass('green').text('Active'); break; case 2: return $('<span>').addClass('orange').text('Inactive'); break; default: return $('<span>').text(theStream.online); break; } } else { switch (theStream.online) { case -1: return $('<span>').text('Unknown, checking..'); break; case 0: return $('<span>').addClass('red').text(theStream.error); break; case 1: return $('<span>').addClass('green').text(theStream.error); break; case 2: return $('<span>').addClass('orange').text(theStream.error); break; default: return $('<span>').text(theStream.error); break; } } } /** * Apply entered data in input and select fields to their corresponding object adress */ function applyInput(){ //check if all required fields have something in them $('#input-validation-info').remove(); var error = false; $('input.isSetting.validate-required').each(function(){ if ($(this).val() == '') { $(this).focus(); $(this).parent().append( $('<div>').attr('id','input-validation-info').html( 'This is a required field.' ).addClass('red') ); error = true; return false; } }); if (error == true) { return false; } //apply the inputs $('input.isSetting,select.isSetting').each(function(){ var objpath = findObjPath($(this)); if (($(this).val() == '') || (($(this).val() == 0) && ($(this).attr('type') == 'number'))) { eval('delete '+objpath+';'); } else { eval(objpath+' = $(this).val();'); } }); return true; } /** * Apply data from the settings object to the page elements */ //this function has been placed in footer.html /** * Find the path to the setting for this element */ function findObjPath($element) { if ($element.attr('objpath')) { return 'settings.'+$element.attr('objpath'); } else { return 'settings.'+$element.attr('id').replace(/-/g,'.'); } } 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 = {}; data.authorize = $.extend(true,{},settings.credentials); delete data.authorize.authstring; data.authorize.password = settings.credentials.authstring ? MD5(MD5(settings.credentials.password)+settings.credentials.authstring) : ''; $.extend(true,data,sendData); consolelog('['+(new Date).toTimeString().split(' ')[0]+']','Sending data:',data); $('#message').removeClass('red').text('Data sent, waiting for a reply..').append( $('<br>') ).append( $('<a>').text('Cancel request').click(function(){ jqxhr.abort(); }) ); if (doShield) { $('body').append( $('<div>').attr('id', 'shield').text('Loading, please wait..') ); } var obj = { 'url': settings.server, 'type': 'POST', 'data': { 'command': JSON.stringify(data) }, 'dataType': 'jsonp', 'crossDomain': true, 'timeout': timeOut, '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 MistServer:').append( $('<br>') ).append( textStatus ).append( $('<a>').text('Send server request again').click(function(){ getData(callBack,sendData,timeOut,doShield); }) ); $('#shield').remove(); }, 'success': function(returnedData){ $('#message').text('Data received, processing..'); consolelog('['+(new Date).toTimeString().split(' ')[0]+']','Received data:',returnedData); if ((returnedData) && (returnedData.authorize)) { if (returnedData.authorize.challenge){ if (settings.credentials.authstring != returnedData.authorize.challenge){ if (returnedData.authorize.status == 'ACC_MADE') { delete settings.credentials.new_password; delete settings.credentials.new_username; $('#page').html( $('<div>').addClass('description').text('Account made! Logging in..') ); } else { $('#page').html( $('<div>').addClass('description').text('Logging in..') ); } settings.credentials.authstring = returnedData.authorize.challenge; getData(callBack,sendData,timeOut); return; } else { $('#message').addClass('red').text('The credentials you provided are incorrect.'); $('#connection').addClass('red').removeClass('green').text('Disconnected'); showTab('login'); $('#shield').remove(); return; } } else if (returnedData.authorize.status == 'NOACC') { $('#message').addClass('red').text('The server does not have any accounts set.'); $('#connection').addClass('green').removeClass('red').text('Connected'); showTab('create new account'); $('#shield').remove(); return; } else { $('#connection').addClass('green').removeClass('red').text('Connected'); } } if (callBack) { callBack(returnedData); } $('#message').text('Last communication with the server at '+formatTime((new Date).getTime()/1000)); if (returnedData.log) { var lastlog = returnedData.log[returnedData.log.length-1]; $('#message').append( $('<br>') ).append( $('<span>').text( 'Last log entry: '+formatTime(lastlog[0])+' ['+lastlog[1]+'] '+lastlog[2] ).addClass(lastlog[1] == 'WARN' ? 'orange' : '') ); } $('#shield').remove(); } }; 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; delete sendData.statistics; if (settings.credentials.authstring) { sendData.capabilities = true; sendData = $.extend(true,{conversion: {'encoders': true, 'status': true}},sendData); } getData(function(returnedData){ settings.settings = returnedData; if (tabName) { showTab(tabName); } },sendData,0,true); } var countrylist = {'AF':'Afghanistan','AX':'Åland Islands','AL':'Albania','DZ':'Algeria','AS':'American Samoa','AD':'Andorra', 'AO':'Angola','AI':'Anguilla','AQ':'Antarctica','AG':'Antigua and Barbuda','AR':'Argentina','AM':'Armenia','AW':'Aruba', 'AU':'Australia','AT':'Austria','AZ':'Azerbaijan','BS':'Bahamas','BH':'Bahrain','BD':'Bangladesh','BB':'Barbados', 'BY':'Belarus','BE':'Belgium','BZ':'Belize','BJ':'Benin','BM':'Bermuda','BT':'Bhutan','BO':'Bolivia, Plurinational State of', 'BQ':'Bonaire, Sint Eustatius and Saba','BA':'Bosnia and Herzegovina','BW':'Botswana','BV':'Bouvet Island','BR':'Brazil', 'IO':'British Indian Ocean Territory','BN':'Brunei Darussalam','BG':'Bulgaria','BF':'Burkina Faso','BI':'Burundi','KH':'Cambodia', 'CM':'Cameroon','CA':'Canada','CV':'Cape Verde','KY':'Cayman Islands','CF':'Central African Republic','TD':'Chad','CL':'Chile', 'CN':'China','CX':'Christmas Island','CC':'Cocos (Keeling) Islands','CO':'Colombia','KM':'Comoros','CG':'Congo', 'CD':'Congo, the Democratic Republic of the','CK':'Cook Islands','CR':'Costa Rica','CI':'Côte d\'Ivoire','HR':'Croatia', 'CU':'Cuba','CW':'Curaçao','CY':'Cyprus','CZ':'Czech Republic','DK':'Denmark','DJ':'Djibouti','DM':'Dominica', 'DO':'Dominican Republic','EC':'Ecuador','EG':'Egypt','SV':'El Salvador','GQ':'Equatorial Guinea','ER':'Eritrea','EE':'Estonia', 'ET':'Ethiopia','FK':'Falkland Islands (Malvinas)','FO':'Faroe Islands','FJ':'Fiji','FI':'Finland','FR':'France','GF':'French Guiana', 'PF':'French Polynesia','TF':'French Southern Territories','GA':'Gabon','GM':'Gambia','GE':'Georgia','DE':'Germany','GH':'Ghana', 'GI':'Gibraltar','GR':'Greece','GL':'Greenland','GD':'Grenada','GP':'Guadeloupe','GU':'Guam','GT':'Guatemala','GG':'Guernsey', 'GN':'Guinea','GW':'Guinea-Bissau','GY':'Guyana','HT':'Haiti','HM':'Heard Island and McDonald Islands', 'VA':'Holy See (Vatican City State)','HN':'Honduras','HK':'Hong Kong','HU':'Hungary','IS':'Iceland','IN':'India','ID':'Indonesia', 'IR':'Iran, Islamic Republic of','IQ':'Iraq','IE':'Ireland','IM':'Isle of Man','IL':'Israel','IT':'Italy','JM':'Jamaica', 'JP':'Japan','JE':'Jersey','JO':'Jordan','KZ':'Kazakhstan','KE':'Kenya','KI':'Kiribati', 'KP':'Korea, Democratic People\'s Republic of','KR':'Korea, Republic of','KW':'Kuwait','KG':'Kyrgyzstan', 'LA':'Lao People\'s Democratic Republic','LV':'Latvia','LB':'Lebanon','LS':'Lesotho','LR':'Liberia','LY':'Libya', 'LI':'Liechtenstein','LT':'Lithuania','LU':'Luxembourg','MO':'Macao','MK':'Macedonia, the former Yugoslav Republic of', 'MG':'Madagascar','MW':'Malawi','MY':'Malaysia','MV':'Maldives','ML':'Mali','MT':'Malta','MH':'Marshall Islands', 'MQ':'Martinique','MR':'Mauritania','MU':'Mauritius','YT':'Mayotte','MX':'Mexico','FM':'Micronesia, Federated States of', 'MD':'Moldova, Republic of','MC':'Monaco','MN':'Mongolia','ME':'Montenegro','MS':'Montserrat','MA':'Morocco','MZ':'Mozambique', 'MM':'Myanmar','NA':'Namibia','NR':'Nauru','NP':'Nepal','NL':'Netherlands','NC':'New Caledonia','NZ':'New Zealand','NI':'Nicaragua', 'NE':'Niger','NG':'Nigeria','NU':'Niue','NF':'Norfolk Island','MP':'Northern Mariana Islands','NO':'Norway','OM':'Oman', 'PK':'Pakistan','PW':'Palau','PS':'Palestine, State of','PA':'Panama','PG':'Papua New Guinea','PY':'Paraguay','PE':'Peru', 'PH':'Philippines','PN':'Pitcairn','PL':'Poland','PT':'Portugal','PR':'Puerto Rico','QA':'Qatar','RE':'Réunion', 'RO':'Romania','RU':'Russian Federation','RW':'Rwanda','BL':'Saint Barthélemy','SH':'Saint Helena, Ascension and Tristan da Cunha', 'KN':'Saint Kitts and Nevis','LC':'Saint Lucia','MF':'Saint Martin (French part)','PM':'Saint Pierre and Miquelon', 'VC':'Saint Vincent and the Grenadines','WS':'Samoa','SM':'San Marino','ST':'Sao Tome and Principe','SA':'Saudi Arabia', 'SN':'Senegal','RS':'Serbia','SC':'Seychelles','SL':'Sierra Leone','SG':'Singapore','SX':'Sint Maarten (Dutch part)','SK':'Slovakia', 'SI':'Slovenia','SB':'Solomon Islands','SO':'Somalia','ZA':'South Africa','GS':'South Georgia and the South Sandwich Islands', 'SS':'South Sudan','ES':'Spain','LK':'Sri Lanka','SD':'Sudan','SR':'Suriname','SJ':'Svalbard and Jan Mayen','SZ':'Swaziland', 'SE':'Sweden','CH':'Switzerland','SY':'Syrian Arab Republic','TW':'Taiwan, Province of China','TJ':'Tajikistan', 'TZ':'Tanzania, United Republic of','TH':'Thailand','TL':'Timor-Leste','TG':'Togo','TK':'Tokelau','TO':'Tonga', 'TT':'Trinidad and Tobago','TN':'Tunisia','TR':'Turkey','TM':'Turkmenistan','TC':'Turks and Caicos Islands','TV':'Tuvalu', 'UG':'Uganda','UA':'Ukraine','AE':'United Arab Emirates','GB':'United Kingdom','US':'United States', 'UM':'United States Minor Outlying Islands','UY':'Uruguay','UZ':'Uzbekistan','VU':'Vanuatu','VE':'Venezuela, Bolivarian Republic of', 'VN':'Viet Nam','VG':'Virgin Islands, British','VI':'Virgin Islands, U.S.','WF':'Wallis and Futuna','EH':'Western Sahara','YE':'Yemen', 'ZM':'Zambia','ZW':'Zimbabwe' }; function updateOverview() { getData(function(data){ var viewers = 0; var streams = 0; var streamsOnline = 0; if (data.clients && data.clients.data) { viewers = data.clients.data.length; } for (var index in data.streams) { streams++; if ((data.streams[index].online) && (data.streams[index].online != 0)) { streamsOnline++; } } $('#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){ if (!data.config.protocols) { data.config.protocols = []; } if (data.config.protocols.length != $('#protocols-tbody').children().length) { saveAndReload('protocols'); return; } for (var index in data.config.protocols) { $('#status-of-'+index).html(formatStatus(data.config.protocols[index])) } }); } 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] != '')) { settingsList.push(index+': '+theProtocol[index]); } else { if (capabilities.required[index]['default']) { settingsList.push(index+': '+capabilities.required[index]['default']); } } } for (var index in capabilities.optional) { if ((theProtocol[index]) && (theProtocol[index] != '')) { settingsList.push(index+': '+theProtocol[index]); } else { if (capabilities.optional[index]['default']) { settingsList.push(index+': '+capabilities.optional[index]['default']); } } } return settingsList.join(', '); } function buildProtocolFields(selectedProtocol,objpath,streamName) { data = settings.settings.capabilities.connectors[selectedProtocol]; $('#protocol-description').html(data.desc); if (data.deps) { $ul = $('<ul>'); $('#protocol-description').append('<br>Dependencies:').append($ul); var dependencies = data.deps.split(','); for (var index in dependencies) { if ($.inArray(dependencies[index],currentConnectors) < 0) { $ul.append( $('<li>').text(dependencies[index]).append( $('<span>').text(' (Not yet configured)').addClass('red') ) ); } else { $ul.append( $('<li>').text(dependencies[index]).append( $('<span>').text(' (Configured)').addClass('green') ) ); } } } $('#protocol-fields').html(''); if (data.required) { $('#protocol-fields').append( $('<p>').text('Required parameters') ).append( buildProtocolParameterFields(data.required,true,objpath) ); } if (data.optional) { $('#protocol-fields').append( $('<p>').text('Optional parameters') ).append( buildProtocolParameterFields(data.optional,false,objpath) ); } if ((streamName != '_new_') && (selectedProtocol == settings.settings.config.protocols[streamName].connector)) { enterSettings(); } } function buildProtocolParameterFields(data,required,objpath) { var $container = $('<div>'); for (var index in data) { var $label = $('<label>').text(data[index].name+':').attr('title',data[index].help+'.').attr('for','protocol-fieldname') var $input = $('<input>').attr('type','text').attr('id','protocol-'+index).attr('objpath',objpath+'.'+index).addClass('isSetting'); switch (data[index].type) { case 'int': case 'uint': $input.addClass('validate-positive-integer'); break; case 'str': default: break; } if (required) { $input.addClass('validate-required'); } if (data[index]['default']) { $input.attr('placeholder',data[index]['default']) } $label.append($input); $container.append($label); } 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],' ')); } settings.settings.statistics = data.statistics; },{clients:{}}); } function filterTable() { var displayRecorded = $('#stream-filter-recorded').is(':checked'); var displayLive = $('#stream-filter-live').is(':checked'); $('#streams-tbody').children().each(function(){ var type = $(this).children('.isLive').text(); if (type == 'Live') { if (displayLive) { $(this).show(); } else { $(this).hide(); } } else { if (displayRecorded) { $(this).show(); } else { $(this).hide(); } } }); } function limitShortToLong(shortLimit) { switch (shortLimit) { case 'kbps_max': return 'Maximum bandwidth'; break; case 'users' : return 'Maximum connected users'; break; case 'geo' : return 'Geo-limited'; break; case 'host' : return 'Host-limited'; break; } } function limitValueFormat(theLimit) { switch (theLimit.name) { case 'kbps_max': return seperateThousands(theLimit.value,' ')+' bytes/s'; break; case 'users': return theLimit.value; break; case 'geo': var str; if (theLimit.value.charAt(0) == '+') { str = 'Whitelist<br>' } else { str = 'Blacklist<br>' } var values = theLimit.value.substr(1).split(' '); for (var index in values) { values[index] = countrylist[values[index]]; } return str+values.join(', ') break; case 'host': var str; if (theLimit.value.charAt(0) == '+') { str = 'Whitelist<br>' } else { str = 'Blacklist<br>' } return str+theLimit.value.substr(1).split(' ').join(', '); break; default: return ''; break; } } function makeLimitValue() { var values = []; $('#field_container').children('input,select').each(function(){ if ($(this).val()) { values.push($(this).val()); } }); $('#limit-value').val($('#limit-list-type').val()+values.join(' ')); } function changeLimitName(limitValue) { if (!limitValue) { limitValue = ''; } $('#limit-value').val(limitValue).removeClass('validate-positive-integer').parent().children('.unit').text(''); $('#detailed-settings').html(''); $('#limit-value-label').show(); var $listtype = $('<select>').attr('id','limit-list-type').html( $('<option>').val('-').text('Blacklist') ).append( $('<option>').val('+').text('Whitelist') ).change(function(){ makeLimitValue(); }); switch ($('#limit-name').val()) { case 'kbps_max': $('#limit-value').addClass('validate-positive-integer').parent().children('.unit').text('[bytes/s]'); break; case 'users': $('#limit-value').addClass('validate-positive-integer'); break; case 'geo': $('#limit-value-label').hide(); var $field = $('<select>').html( $('<option>').val('').text('[Select a country]') ).bind('change',function(){ makeLimitValue(); }); for (var index in countrylist) { $field.append( $('<option>').val(index).html(countrylist[index]) ); } $('#detailed-settings').html( $('<label>').attr('for','limit-list-type').text('List type:').append( $listtype ) ).append( $('<span>').addClass('pretend-label').attr('id','field_container').text('Values:').css('overflow','hidden') ); // ^ can't actually be a label because issues arise with multiple inputs in one label switch (limitValue.charAt(0)) { case '+': $listtype.val('+'); break; case '-': $listtype.val('-'); break; } limitValue = limitValue.substr(1).split(' '); for (var index in limitValue) { $('#field_container').append( $field.clone(true).val(limitValue[index]) ) } $('#field_container').append( $field.clone(true) ); $('#detailed-settings').append( $('<button>').text('Add country list element').click(function(){ $('#field_container').append( $field.clone(true) ); }) ); makeLimitValue(); break; case 'host': $('#limit-value-label').hide(); var $field = $('<input>').attr('type','text').bind('change',function(){ makeLimitValue(); }); $('#detailed-settings').html( $('<label>').attr('for','limit-list-type').text('List type:').append( $listtype ) ).append( $('<span>').addClass('pretend-label').attr('id','field_container').text('Values:').css('overflow','hidden') ); // ^ can't actually be a label because issues arise with multiple inputs in one label switch (limitValue.charAt(0)) { case '+': $listtype.val('+'); break; case '-': $listtype.val('-'); break; } limitValue = limitValue.substr(1).split(' '); for (var index in limitValue) { $('#field_container').append( $field.clone(true).val(limitValue[index]) ) } $('#field_container').append( $field.clone(true) ); $('#detailed-settings').append( $('<button>').text('Add host list element').click(function(){ $('#field_container').append( $field.clone(true) ); }) ); makeLimitValue(); break; } } function formatConversionStatus(status) { if (typeof status == 'string') { if (status == 'Conversion successful') { return $('<span>').addClass('green').text(status); } else { return $('<span>').addClass('red').text(status); } } else { return $('<span>').addClass('orange').text('Converting.. ('+status.progress+'%)'); } } function updateConversions() { getData(function(data){ for (var index in data.conversion.status) { $('#conversion-status-of-'+index).html(formatConversionStatus(data.conversion.status[index])) } settings.settings.conversion.status = data.conversion.status; },{conversion: {status: true}}); } function conversionDirQuery(query){ defaults.conversion.inputdir = query; $('#query-status').text('Searching for input files in "'+query+'"..'); $('#conversion-input-file').html(''); $('#conversion-details').html(''); var objpath = 'settings-conversion-convert-_new_'; var theFiles = {}; getData(function(data){ if (data.conversion.query) { var $inputfile = $('<select>').attr('id',objpath+'-input').addClass('isSetting').addClass('validate-required').change(function(){ conversionSelectInput(theFiles); }); dir = query.replace(/\/$/,'')+'/'; $('#conversion-input-file').html( $('<p>').text('Files from "'+dir+'"') ).append( $('<div>').addClass('description').text('Next, select the file you wish to convert.') ).append( $('<label>').text('Input file:').attr('for',objpath+'-input').addClass('isSetting').append( $inputfile ) ); var filesFound = {total:0,valid:0} for (var index in data.conversion.query) { filesFound.total++; if ((data.conversion.query[index]) && (index.substr(-5) != '.dtsc')) { filesFound.valid++; $inputfile.append( $('<option>').text(index).val(dir+index) ); theFiles[index] = data.conversion.query[index]; } } $('#query-status').text(filesFound.total+' file(s) found, of which '+filesFound.valid+' are valid.'); $inputfile.children().sort(function(a,b){ return ($(a).text().toLowerCase() > $(b).text().toLowerCase()) ? 1 : -1; }).appendTo($inputfile); if (filesFound.valid > 0) { conversionSelectInput(theFiles); } } else { $('#query-status').text('No files found in "'+query+'".'); } },{conversion:{query:{path:query}}},60000); //1 minute timeout } function conversionSelectInput(theFiles) { var objpath = 'settings-conversion-convert-_new_'; var index = $('#'+objpath+'-input option:selected').text(); var filename = index.split('.'); filename.splice(-1); filename = filename.join('.'); theFile = theFiles[index]; theFile = $.extend(true,{audio:{samplerate:''},video:{fpks:'',width:'',height:''}},theFile); theFile.video.fps = theFile.video.fpks ? theFile.video.fpks/1000 : ''; var $encoders = $('<select>').attr('id',objpath+'-encoder').addClass('isSetting').addClass('validate-required').change(function(){ $videocodec.html( $('<option>').text('Current').val('') ); for (var codec in settings.settings.conversion.encoders[$(this).val()].video) { $videocodec.append( $('<option>').val(codec).text(settings.settings.conversion.encoders[$(this).val()].video[codec]+' ('+codec+')') ); } $audiocodec.html( $('<option>').text('Current').val('') ); for (var codec in settings.settings.conversion.encoders[$(this).val()].audio) { $audiocodec.append( $('<option>').val(codec).text(settings.settings.conversion.encoders[$(this).val()].audio[codec]+' ('+codec+')') ); } }); var $videocodec = $('<select>').attr('id',objpath+'-video-codec').addClass('isSetting').change(function(){ if ($(this).val() == '') { $('#video-settings-container').find('input,select').not($(this)).val('').removeClass('isSetting').parent().hide(); } else { $('#video-settings-container').find('input,select').not($(this)).addClass('isSetting').parent().show(); } }); var $audiocodec = $('<select>').attr('id',objpath+'-audio-codec').addClass('isSetting').change(function(){ if ($(this).children(':selected').text().substr(0,4) == 'mp3 ') { $('#settings-conversion-convert-_new_-audio-samplerate').val('44100').attr('disabled','disabled'); } else { $('#settings-conversion-convert-_new_-audio-samplerate').removeAttr('disabled'); if ($(this).val() == '') { $('#audio-settings-container').find('input,select').not($(this)).val('').removeClass('isSetting').parent().hide(); } else { $('#audio-settings-container').find('input,select').not($(this)).addClass('isSetting').parent().show(); } } }); $('#conversion-details').html( $('<p>').text('Conversion settings for "'+index+'"') ).append( $('<div>').addClass('description').text('Finally, select the file properties you wish to convert to.') ).append( $('<label>').text('Output directory:').attr('for',objpath+'-outputdir').append( $('<input>').attr('type','text').attr('id',objpath+'-outputdir').val(dir).attr('placeholder','/path/to/output/folder').addClass('isSetting').addClass('validate-required') ) ).append( $('<label>').text('Output filename:').attr('for',objpath+'-output').append( $('<input>').attr('type','text').attr('id',objpath+'-output').val(filename+'.dtsc').attr('placeholder','name_of_output.file').addClass('isSetting').addClass('validate-required') ) ).append( $('<label>').text('Encoder:').attr('for',objpath+'-encoder').append( $encoders ) ).append( $('<label>').text('Include video:').attr('for',objpath+'-video').append( $('<input>').attr('type','checkbox').attr('id',objpath+'-video').attr('checked','checked').change(function(){ if (!$(this).is(':checked')) { $('#video-settings-container').hide().find('input,select').val('').removeClass('isSetting'); } else { $('#video-settings-container').show().find('input,select').addClass('isSetting'); } $videocodec.trigger('change'); }) ) ).append( $('<span>').attr('id','video-settings-container').html( $('<label>').text('Video codec:').attr('for',objpath+'-video-codec').append( $videocodec ) ).append( $('<label>').text('Video FPS:').attr('for',objpath+'-video-fps').append( $('<input>').attr('type','text').attr('id',objpath+'-video-fps').attr('placeholder',theFile.video.fps).addClass('isSetting').addClass('validate-positive-number') ) ).append( $('<label>').text('Video width:').attr('for',objpath+'-video-width').append( $('<span>').addClass('unit').text('[px]') ).append( $('<input>').attr('type','text').attr('id',objpath+'-video-width').attr('placeholder',theFile.video.width).addClass('isSetting').addClass('validate-positive-integer') ) ).append( $('<label>').text('Video height:').attr('for',objpath+'-video-height').append( $('<span>').addClass('unit').text('[px]') ).append( $('<input>').attr('type','text').attr('id',objpath+'-video-height').attr('placeholder',theFile.video.height).addClass('isSetting').addClass('validate-positive-integer') ) ) ).append( $('<label>').text('Include audio:').attr('for',objpath+'-audio').append( $('<input>').attr('type','checkbox').attr('id',objpath+'-audio').attr('checked','checked').change(function(){ if (!$(this).is(':checked')) { $('#audio-settings-container').hide().find('input,select').val('').removeClass('isSetting'); } else { $('#audio-settings-container').show().find('input,select').addClass('isSetting'); } $audiocodec.trigger('change'); }) ) ).append( $('<span>').attr('id','audio-settings-container').html( $('<label>').text('Audio codec:').attr('for',objpath+'-audio-codec').append( $audiocodec ) ).append( $('<label>').text('Audio sample rate:').attr('for',objpath+'-audio-samplerate').append( $('<span>').addClass('unit').text('[Hz]') ).append( $('<input>').attr('type','text').attr('id',objpath+'-audio-samplerate').attr('placeholder',seperateThousands(theFile.audio.samplerate,' ')).addClass('isSetting').addClass('validate-positive-integer') ) ) ).append( $('<button>').text('Save').addClass('enter-to-submit').click(function(){ if (!settings.settings.conversion.convert) { settings.settings.conversion.convert = {}; } settings.settings.conversion.convert._new_= {}; if ($('#'+objpath+'-video').is(':checked')) { settings.settings.conversion.convert._new_.video = {}; } if ($('#'+objpath+'-audio').is(':checked')) { settings.settings.conversion.convert._new_.audio = {}; } 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'); }) ).append( $('<button>').text('Cancel').addClass('escape-to-cancel').click(function(){ showTab('conversion'); }) ); for (var encoder in settings.settings.conversion.encoders) { $encoders.append( $('<option>').text(encoder).val(encoder) ); } $encoders.trigger('change'); $audiocodec.trigger('change'); $videocodec.trigger('change'); } function buildLogsTable(){ var logs = settings.settings.log; if ((logs.length >= 2) && (logs[0][0] < logs[logs.length-1][0])){ logs.reverse(); } var $tbody = $('<tbody>'); for (var index in logs) { $tbody.append( $('<tr>').html( $('<td>').text(formatDateLong(logs[index][0])) ).append( $('<td>').text(logs[index][1]) ).append( $('<td>').text(logs[index][2]) ) ); } return $('<table>').attr('id','logs-table').html( $('<thead>').html( $('<tr>').html( $('<th>').text('Date') ).append( $('<th>').text('Type') ).append( $('<th>').text('Message') ) ) ).append($tbody); } function fillServerstatsTables(data) { if (data.capabilities.mem) { $('#stats-physical-memory').html( $('<tr>').html( $('<td>').text('Used:') ).append( $('<td>').html(seperateThousands(data.capabilities.mem.used,' ')+' MiB<br>('+data.capabilities.load.memory+'%)').css('text-align','right') ) ).append( $('<tr>').html( $('<td>').text('Cached:') ).append( $('<td>').text(seperateThousands(data.capabilities.mem.cached,' ')+' MiB').css('text-align','right') ) ).append( $('<tr>').html( $('<td>').text('Available:') ).append( $('<td>').text(seperateThousands(data.capabilities.mem.free,' ')+' MiB').css('text-align','right') ) ).append( $('<tr>').html( $('<td>').text('Total:') ).append( $('<td>').text(seperateThousands(data.capabilities.mem.total,' ')+' MiB').css('text-align','right') ) ); $('#stats-swap-memory').html( $('<tr>').html( $('<td>').text('Used:') ).append( $('<td>').text(seperateThousands(data.capabilities.mem.swaptotal-data.capabilities.mem.swapfree,' ')+' MiB').css('text-align','right') ) ).append( $('<tr>').html( $('<td>').text('Available:') ).append( $('<td>').text(seperateThousands(data.capabilities.mem.swapfree,' ')+' MiB').css('text-align','right') ) ).append( $('<tr>').html( $('<td>').text('Total:') ).append( $('<td>').text(seperateThousands(data.capabilities.mem.swaptotal,' ')+' MiB').css('text-align','right') ) ); } if (data.capabilities.load) { $('#stats-loading').html( $('<tr>').html( $('<td>').text('1 minute:') ).append( $('<td>').text(settings.settings.capabilities.load.one+'%').css('text-align','right') ) ).append( $('<tr>').html( $('<td>').text('5 minutes:') ).append( $('<td>').text(settings.settings.capabilities.load.five+'%').css('text-align','right') ) ).append( $('<tr>').html( $('<td>').text('15 minutes:') ).append( $('<td>').text(settings.settings.capabilities.load.fifteen+'%').css('text-align','right') ) ); } } function updateServerstats() { getData(function(data){ fillServerstatsTables(data); settings.settings.capabilities = data.capabilities; },{capabilities:true}); } function buildstreamembed(streamName,embedbase) { $('#liststreams .button.current').removeClass('current') $('#liststreams .button').filter(function(){ return $(this).text() == streamName; }).addClass('current'); $('#subpage').html( $('<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(){ $('#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) { case 13: //the return key $(document.activeElement).parents('.input_container').children('.enter-to-submit').trigger('click'); break; case 27: //the escape key $('.escape-to-cancel').trigger('click'); break; } }); $('#page').on('input','.validate-positive-integer',function(e){ var curpos = this.selectionStart; //store the cursor position var v = $(this).val(); //validate the current value v = v.replace(/[^\d]/g,''); if (Number(v) == 0) { v = ''; } $('#input-validation-info').remove(); if (v != $(this).val()) { $(this).parent().append( $('<div>').attr('id','input-validation-info').html( 'Invalid input.<br>Input in this field must be a positive nonzero integer.' ).addClass('red') ); } $(this).val(v); this.setSelectionRange(curpos,curpos); //set the cursor to its original position }); $('#page').on('input','.validate-positive-number',function(e){ var curpos = this.selectionStart; //store the cursor position var v = $(this).val(); //validate the current value v = v.replace(/[^\d\.]/g,''); v = v.split('.'); if (v.length > 1) { var decimal = v.splice(1).join(''); v.push(decimal); } v = v.join('.'); if (Number(v) == 0) { v = ''; } $('#input-validation-info').remove(); if (v != $(this).val()) { $(this).parent().append( $('<div>').attr('id','input-validation-info').html( 'Invalid input.<br>Input in this field must be a positive nonzero number.' ).addClass('red') ); } $(this).val(v); this.setSelectionRange(curpos,curpos); //set the cursor to its original position }); $('#page').on('input','.validate-lowercase-alphanumeric_-firstcharNaN',function(e){ var curpos = this.selectionStart; var v = $(this).val(); v = v.toLowerCase(); //make everything lowercase v = v.replace(/[^\da-z_]/g,''); //remove any chars except for digits, lowercase letters and underscores v = v.replace(/^\d+/,''); //remove any digits at the front of the string $('#input-validation-info').remove(); if (v != $(this).val()) { $(this).parent().append( $('<div>').attr('id','input-validation-info').html( 'Invalid input.<br>Input in this field must be:<ul><li>lowercase</li><li>alphanumeric or underscore</li><li>the first character can\'t be numerical</li></ul>' ).addClass('red') ); } $(this).val(v); this.setSelectionRange(curpos,curpos); }); $('.expandbutton').click(function(){ $(this).toggleClass('active'); }); $('#ih-button').click(function(){ $('.ih-balloon').remove(); if (!ih) { 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) { if (ignoreHashChange) { ignoreHashChange = false; return; } var loc = location.hash.split('&')[1].split('@'); if (loc[1]) { showTab(loc[0],loc[1]); } else { showTab(loc[0]); } ignoreHashChange = false; }); function localStorageSupported() { //does this browser support it? try { return 'localStorage' in window && window['localStorage'] !== null; } catch (e) { return false; } } /* functions for the statistics graphs */ function getPlotData(graphs) { var reqobj = { totals: [] }; for (var g in graphs) { for (var d in graphs[g].datasets) { var set = graphs[g].datasets[d]; switch (set.type) { case 'clients': case 'upbps': case 'downbps': switch (set.cumutype) { case 'all': reqobj['totals'].push({fields: [set.type]}); break; case 'stream': reqobj['totals'].push({fields: [set.type], streams: [set.stream]}); break; case 'protocol': reqobj['totals'].push({fields: [set.type], protocols: [set.protocol]}); break; } set.sourceid = reqobj['totals'].length-1; break; case 'cpuload': case 'memload': reqobj['capabilities'] = {}; break; } } } 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]); } },reqobj); } 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[dataobj.sourceid] || !sourcedata.totals[dataobj.sourceid].data) { dataobj.data.push([(now-600)*1000,0]); dataobj.data.push([now*1000,0]); } else { var fields = {}; for (var index in sourcedata.totals[dataobj.sourceid].fields) { fields[sourcedata.totals[dataobj.sourceid].fields[index]] = index; } var time = sourcedata.totals[dataobj.sourceid].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[dataobj.sourceid].data[index][fields[dataobj.type]]]]); for (var i in sourcedata.totals[dataobj.sourceid].interval) { if ((i % 2) == 1) { //fill gaps with 0 time += sourcedata.totals[dataobj.sourceid].interval[i][1]; dataobj.data.push([time*1000,0]); } else { for (var j = 0; j < sourcedata.totals[dataobj.sourceid].interval[i][0]; j++) { time += sourcedata.totals[dataobj.sourceid].interval[i][1]; index++; dataobj.data.push([time*1000,sourcedata.totals[dataobj.sourceid].data[index][fields[dataobj.type]]]); } if (i < sourcedata.totals[dataobj.sourceid].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; case 'coords': //retrieve data //format [lat,long] //testing data dataobj.data = [[-54.657438,-65.11675],[49.725719,-1.941553],[-34.425464,172.677617],[76.958669,68.494178],[0,0]]; break; } return dataobj; } function drawGraph(graph){ var datasets = graph.datasets; if (datasets.length < 1) { $('#'+graph.id).children('.graph,.legend').html(''); return; } switch (graph.type) { case 'coords': plotsets = []; for (var d in datasets) { //put backend data into the correct projection data = datasets[d].data; //correct latitude according to the Miller cylindrical projection for (var i in data) { var lat = data[i][0]; var lon = data[i][1]; //to radians lat = Math.PI * lat / 180; var y = 1.25 * Math.log(Math.tan(0.25 * Math.PI + 0.4 * lat)); data[i] = [lon,y]; } plotsets.push({data:data}); } //make sure the plot area has the correct height/width ratio if ($('#'+graph.id+' .graphbackground').length == 0) { var parent = $('#'+graph.id+' .graph'); var mapheight = 2450; var mapwidth = 3386.08; parent.height(parent.width()*mapheight/mapwidth); var placeholder = $('<div>').addClass('graphforeground') parent.html( $('<img>').attr('src','map.svg').addClass('graphbackground').width(parent.width).height(parent.height()) ).append( placeholder ); } else { var placeholder = $('#'+graph.id+' .graphforeground'); } plot = $.plot( placeholder, plotsets, { legend: {show: false}, xaxis: { show: false, min: -170, max: 190 }, yaxis: { show: false, min: -2.248101053, max: 2.073709536 }, series: { lines: {show: false}, points: {show: true} }, grid: { hoverable: true, margin: 0, border: 0, borderWidth: 0, minBorderMargin: 0 } } ); break; case 'time': case 'default': 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']} } } ); break; } //end of graph type switch $('#'+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'}) ); } }