/** * logging */ define([ 'jquery', 'app/init', 'app/util', 'bootbox' ], function($, Init, Util, bootbox) { 'use strict'; let logData = []; // cache object for all log entries let logDataTable = null; // "Datatables" Object // Morris charts data let maxGraphDataCount = 30; // max date entries for a graph let chartData = {}; // chart Data object for all Morris Log graphs let config = { taskDialogId: 'pf-task-dialog', // id for map "task manager" dialog dialogDynamicAreaClass: 'pf-dynamic-area', // class for dynamic areas timestampCounterClass: 'pf-timestamp-counter', // class for "timestamp" counter taskDialogStatusAreaClass: 'pf-task-dialog-status', // class for "status" dynamic area taskDialogLogTableAreaClass: 'pf-task-dialog-table', // class for "log table" dynamic area logGraphClass: 'pf-log-graph', // class for all log Morris graphs tableToolsClass: 'pf-table-tools' // class for table tools }; /** * get log time string * @returns {string} */ let getLogTime = function(){ let serverTime = Util.getServerTime(); let logTime = serverTime.toLocaleTimeString('en-US', { hour12: false }); return logTime; }; /** * updated "sync status" dynamic dialog area */ let updateSyncStatus = function(){ // check if task manager dialog is open let logDialog = $('#' + config.taskDialogId); if(logDialog.length){ // dialog is open requirejs(['text!templates/modules/sync_status.html', 'mustache'], function(templateSyncStatus, Mustache) { let data = { timestampCounterClass: config.timestampCounterClass, syncStatus: Init.syncStatus, isWebSocket: () => { return (Util.getSyncType() === 'webSocket'); }, isAjax: () => { return (Util.getSyncType() === 'ajax'); } }; let syncStatusElement = $( Mustache.render(templateSyncStatus, data ) ); logDialog.find('.' + config.taskDialogStatusAreaClass).html( syncStatusElement ); logDialog.find('.' + config.timestampCounterClass).initTimestampCounter(); syncStatusElement.initTooltips({ placement: 'right' }); }); } }; /** * shows the logging dialog */ let showDialog = function(){ // dialog content requirejs(['text!templates/dialog/task_manager.html', 'mustache', 'datatables.loader'], function(templateTaskManagerDialog, Mustache) { let data = { id: config.taskDialogId, dialogDynamicAreaClass: config.dialogDynamicAreaClass, taskDialogStatusAreaClass: config.taskDialogStatusAreaClass, taskDialogLogTableAreaClass: config.taskDialogLogTableAreaClass, tableActionBarClass: config.tableToolsClass }; let contentTaskManager = $( Mustache.render(templateTaskManagerDialog, data) ); let rowElementGraphs = contentTaskManager.find('.row'); let taskDialogLogTableAreaElement = contentTaskManager.find('.' + config.taskDialogLogTableAreaClass); let logTable = $('', { class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ') }); taskDialogLogTableAreaElement.append(logTable); // init log table logDataTable = logTable.DataTable({ paging: true, ordering: true, order: [ 1, 'desc' ], autoWidth: false, hover: false, pageLength: 10, lengthMenu: [[5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, 'All']], data: logData, // load cached logs (if available) language: { emptyTable: 'No entries', zeroRecords: 'No entries found', lengthMenu: 'Show _MENU_ entries', info: 'Showing _START_ to _END_ of _TOTAL_ entries' }, columnDefs: [ { targets: 0, title: '', width: '18px', searchable: false, class: ['text-center'].join(' '), data: 'status' },{ targets: 1, title: '  ', width: '50px', searchable: true, class: 'text-right', data: 'time' },{ targets: 2, title: '  ', width: '35px', searchable: false, class: 'text-right', sType: 'html', data: 'duration' },{ targets: 3, title: 'description', searchable: true, data: 'description' },{ targets: 4, title: 'type', width: '40px', searchable: true, class: ['text-center'].join(' '), data: 'type' },{ targets: 5, title: 'Prozess-ID   ', width: '80px', searchable: false, class: 'text-right', data: 'key' } ] }); // open dialog let logDialog = bootbox.dialog({ title: 'Task-Manager', message: contentTaskManager, size: 'large', buttons: { close: { label: 'close', className: 'btn-default' } } }); // modal dialog is shown logDialog.on('shown.bs.modal', function(e) { updateSyncStatus(); // show Morris graphs ---------------------------------------------------------- // function for chart label formation let labelYFormat = function(y){ return Math.round(y) + 'ms'; }; for(let key in chartData) { if(chartData.hasOwnProperty(key)) { // create a chart for each key let colElementGraph = $('
', { class: ['col-md-6'].join(' ') }); // graph element let graphElement = $('
', { class: config.logGraphClass }); let graphArea = $('
', { class: config.dialogDynamicAreaClass }).append( graphElement ); let headline = $('

', { text: key }).prepend( $('', { class: ['txt-color', 'txt-color-grayLight'].join(' '), text: 'Prozess-ID: ' }) ); // show update ping between function calls let updateElement = $('', { class: ['txt-color', 'txt-color-blue', 'pull-right'].join(' ') }); headline.append(updateElement).append('
'); // show average execution time let averageElement = $('', { class: 'pull-right' }); headline.append(averageElement); colElementGraph.append( headline ); colElementGraph.append( graphArea ); graphArea.showLoadingAnimation(); rowElementGraphs.append( colElementGraph ); // cache DOM Elements that will be updated frequently chartData[key].averageElement = averageElement; chartData[key].updateElement = updateElement; chartData[key].graph = Morris.Area({ element: graphElement, data: [], xkey: 'x', ykeys: ['y'], labels: [key], units: 'ms', parseTime: false, ymin: 0, yLabelFormat: labelYFormat, padding: 10, hideHover: true, pointSize: 3, lineColors: ['#375959'], pointFillColors: ['#477372'], pointStrokeColors: ['#313335'], lineWidth: 2, grid: false, gridStrokeWidth: 0.3, gridTextSize: 9, gridTextFamily: 'Oxygen Bold', gridTextColor: '#63676a', behaveLikeLine: true, goals: [], goalLineColors: ['#66c84f'], smooth: false, fillOpacity: 0.3, resize: true }); updateLogGraph(key); graphArea.hideLoadingAnimation(); } } // ------------------------------------------------------------------------------ // add dataTable buttons (extension) let buttons = new $.fn.dataTable.Buttons( logDataTable, { buttons: [ { extend: 'copy', className: 'btn btn-sm btn-default', text: ' copy' },{ extend: 'csv', className: 'btn btn-sm btn-default', text: ' csv' } ] } ); logDataTable.buttons().container().appendTo( $(this).find('.' + config.tableToolsClass)); }); // modal dialog is closed logDialog.on('hidden.bs.modal', function(e) { // clear memory -> destroy all charts for (let key in chartData) { if (chartData.hasOwnProperty(key)) { chartData[key].graph = null; } } }); // modal dialog before hide logDialog.on('hide.bs.modal', function(e) { // destroy logTable logDataTable.destroy(true); logDataTable= null; // remove event -> prevent calling this multiple times $(this).off('hide.bs.modal'); }); }); }; /** * updates the log graph for a log key * @param key * @param duration (if undefined -> just update graph with current data) */ let updateLogGraph = function(key, duration){ // check if graph data already exist if( !(chartData.hasOwnProperty(key))){ chartData[key] = {}; chartData[key].data = []; chartData[key].graph = null; chartData[key].averageElement = null; chartData[key].updateElement = null; } // add new value if(duration !== undefined){ chartData[key].data.unshift(duration); } if(chartData[key].data.length > maxGraphDataCount){ chartData[key].data = chartData[key].data.slice(0, maxGraphDataCount); } function getGraphData(data) { let tempChartData = { data: [], dataSum: 0, average: 0 }; for(let x = 0; x < maxGraphDataCount; x++){ let value = 0; if(data[x]){ value = data[x]; tempChartData.dataSum = Number( (tempChartData.dataSum + value).toFixed(2) ); } tempChartData.data.push({ x: x, y: value }); } // calculate average tempChartData.average = Number( ( tempChartData.dataSum / data.length ).toFixed(2) ); return tempChartData; } let tempChartData = getGraphData(chartData[key].data); // add new data to graph (Morris chart) - if is already initialized if(chartData[key].graph !== null){ let avgElement = chartData[key].averageElement; let updateElement = chartData[key].updateElement; let delay = Util.getCurrentTriggerDelay( key, 0 ); if(delay){ updateElement[0].textContent = ' delay: ' + delay + 'ms '; } // set/change average line chartData[key].graph.options.goals = [tempChartData.average]; // change avg. display avgElement[0].textContent = 'Avg. ' + tempChartData.average + 'ms'; let avgStatus = getLogStatusByDuration(key, tempChartData.average); let avgStatusClass = Util.getLogInfo( avgStatus, 'class' ); //change avg. display class if( !avgElement.hasClass(avgStatusClass) ){ // avg status changed! avgElement.removeClass().addClass('pull-right txt-color ' + avgStatusClass); // change goals line color if(avgStatus === 'warning'){ chartData[key].graph.options.goalLineColors = ['#e28a0d']; $(document).setProgramStatus('slow connection'); }else{ chartData[key].graph.options.goalLineColors = ['#5cb85c']; } } // set new data and redraw chartData[key].graph.setData( tempChartData.data ); } return tempChartData.data; }; /** * get the log "status" by log duration (ms). * If duration > warning limit -> show as warning * @param logKey * @param logDuration * @returns {string} */ let getLogStatusByDuration = function(logKey, logDuration){ let logStatus = 'info'; if( logDuration > Init.timer[logKey].EXECUTION_LIMIT ){ logStatus = 'warning'; } return logStatus; }; /** * get the css class for a specific log type * @param logType * @returns {string} */ let getLogTypeIconClass = function(logType){ let logIconClass = ''; switch(logType){ case 'client': logIconClass = 'fa-user'; break; case 'server': logIconClass = 'fa-download'; break; } return logIconClass; }; /** * init logging -> set global log events */ let init = function(){ let maxEntries = 150; $(window).on('pf:syncStatus', function(){ updateSyncStatus(); }); // set global logging listener $(window).on('pf:log', function(e, logKey, options){ // check required logging information if( options && options.duration && options.description ){ let logDescription = options.description; let logDuration = options.duration; let logType = options.type; // check log status by duration let logStatus = getLogStatusByDuration(logKey, logDuration); let statusClass = Util.getLogInfo( logStatus, 'class' ); let typeIconClass = getLogTypeIconClass(logType); // update graph data updateLogGraph(logKey, logDuration); let logRowData = { status: '', time: getLogTime(), duration: '' + logDuration + 'ms', description: logDescription, type: '', key: logKey }; if(logDataTable){ // add row if dataTable is initialized before new log logDataTable.row.add( logRowData ).draw(false); }else{ // add row data to cache logData.push(logRowData); } } // delete old log entries from table --------------------------------- let rowCount = logData.length; if( rowCount >= maxEntries ){ if(logDataTable){ logDataTable.rows(0, {order:'index'}).remove().draw(false); }else{ logData.shift(); } } // cache logs in order to keep previous logs in table after reopening the dialog if(logDataTable){ logData = logDataTable.rows({order:'index'}).data(); } }); }; return { init: init, getLogTime: getLogTime, showDialog: showDialog }; });