Source: app-wrapper/js/helper/debugHelper.js
/**
* @fileOverview DebugHelper class file
* @author Dino Ivankov <dinoivankov@gmail.com>
* @version 1.3.1
*/
const path = require('path');
const _ = require('lodash');
const pusage = require('pidusage');
const AppBaseClass = require('../lib/appBase').AppBaseClass;
var _appWrapper;
var appState;
/**
* DebugHelper class - handles and manages app debugger and debug messages
*
* @class
* @extends {appWrapper.AppBaseClass}
* @memberof appWrapper.helpers
*/
class DebugHelper extends AppBaseClass {
/**
* Creates DebugHelper instance
*
* @constructor
* @return {DebugHelper} Instance of DebugHelper class
*/
constructor() {
super();
if (window && window.getAppWrapper && _.isFunction(window.getAppWrapper)){
_appWrapper = window.getAppWrapper();
appState = _appWrapper.getAppState();
}
this.timeouts = {
processDebugMessagesTimeout: null
};
this.intervals = {
usageData: null
};
this.boundMethods = {
refreshUsageData: null
};
return this;
}
/**
* Opens standalone debug window
*
* @return {undefined}
*/
openDebugWindow (){
this.log('Opening standalone debug window', 'info', []);
_appWrapper.debugWindow = _appWrapper.windowManager.openNewWindow(this.getConfig('debug.debugWindowFile'), {
id: 'debugWindow',
frame: false
});
this.addUserMessage('Debug window opened', 'info', [], false, false);
}
/**
* Prepares standalone debug window data and references to main window data
*
* @async
* @return {window} Reference to debug window
*/
async prepareDebugWindow () {
_appWrapper.debugWindow.appState = _.cloneDeep(appState);
_appWrapper.debugWindow.appState.debugMessages = appState.debugMessages;
_appWrapper.debugWindow.appState.allDebugMessages = appState.allDebugMessages;
_appWrapper.debugWindow.appState.hasDebugWindow = false;
_appWrapper.debugWindow.appState.config = appState.config;
_appWrapper.debugWindow.document.body.className += ' nw-body-initialized';
appState.hasDebugWindow = true;
return _appWrapper.debugWindow;
}
/**
* Toggles debug.hideDebug config variable, showing or hiding app-debug component with debug messages
*
* @return {undefined}
*/
toggleDebug () {
_appWrapper.appConfig.setConfigVar('debug.hideDebug', !appState.config.debug.hideDebug);
}
/**
* Changes minimum debug level for displaying debug messages
*
* @param {Event} e Event that triggered the method
* @return {undefined}
*/
changeDebugLevel(e){
var level = e.target.value;
this.addUserMessage('Changing debug level to "{1}".', 'info', [level], false, false);
_appWrapper.appConfig.setConfigVar('debug.debugLevel', level);
if (appState.isDebugWindow) {
this.addUserMessage('Changing debug level in main window to "{1}".', 'info', [level], false, false);
_appWrapper.mainWindow.appState.config.debug.debugLevel = level;
}
}
/**
* Clears console and all debug messages
*
* @return {undefined}
*/
clearDebugMessages () {
console.clear();
if (appState.isDebugWindow){
_appWrapper.mainWindow.appState.allDebugMessages = [];
_appWrapper.mainWindow.appState.debugMessages = [];
appState.allDebugMessages = _appWrapper.mainWindow.appState.allDebugMessages;
appState.debugMessages = _appWrapper.mainWindow.appState.debugMessages;
} else {
appState.allDebugMessages = [];
appState.debugMessages = [];
if (appState.hasDebugWindow){
_appWrapper.debugWindow.appState.allDebugMessages = appState.allDebugMessages;
_appWrapper.debugWindow.appState.debugMessages = appState.debugMessages;
}
}
this.addUserMessage('Debug messages cleared', 'debug', []);
}
/**
* Handler for save debug button
*
* @param {Event} e Event that triggered the method
* @return {undefined}
*/
saveDebug (e) {
if (e && e.preventDefault && _.isFunction(e.preventDefault)){
e.preventDefault();
}
this.showSaveDebugModal();
}
/**
* Opens modal dialog for saving debug log to file
*
* @async
* @return {undefined}
*/
async showSaveDebugModal () {
let modalHelper = _appWrapper.getHelper('modal');
let modalOptions = {
title: _appWrapper.appTranslations.translate('Saving debug log to file'),
bodyComponent: 'save-debug',
confirmButtonText: _appWrapper.appTranslations.translate('Save'),
cancelButtonText: _appWrapper.appTranslations.translate('Cancel'),
showCancelButton: false,
confirmDisabled: true,
hasHiddenMessages: appState.allDebugMessages.length - appState.debugMessages.length,
saveFileError: false,
defaultFilename: 'debug-' + _appWrapper.getHelper('format').formatDateNormalize(new Date(), false, true) + '.txt',
busy: true,
busyText: _appWrapper.appTranslations.translate('Please wait...'),
onOpen: function() {
let buttonEl = appState.modalData.modalElement.querySelector('.file-picker-button');
if (buttonEl){
buttonEl.focus();
}
},
};
_appWrapper._confirmModalAction = _appWrapper.getHelper('util').confirmSaveLogAction;
modalHelper.openModal('saveDebugModal', modalOptions);
}
/**
* Handler for opening file dialog for saving debug log to file
*
* @param {Event} e Event that triggered the method
* @return {undefined}
*/
saveDebugFileClick (e){
let fileEl = e.target.parentNode.querySelector('.file-picker');
fileEl.setAttribute('nwsaveas', 'debug-' + _appWrapper.getHelper('format').formatDateNormalize(new Date(), false, true) + '.json');
fileEl.click();
}
/**
* Handler that saves debug log to file when related input value has changed
*
* @return {undefined}
*/
saveDebugFileChange () {
let modalHelper = _appWrapper.getHelper('modal');
modalHelper.setModalVar('saveFileError', false);
var modalElement = window.document.querySelector('.modal-dialog');
var fileNameElement = modalElement.querySelector('input[type=file]');
var debugFileName = fileNameElement.value;
var fileValid = true;
modalHelper.clearModalMessages();
modalHelper.modalBusy();
if (!debugFileName){
appState.modalData.currentModal.saveFileError = true;
fileValid = false;
} else {
if (!_appWrapper.fileManager.fileExists(debugFileName)){
appState.modalData.currentModal.fileExists = false;
let dirPath = path.dirname(debugFileName);
if (!_appWrapper.fileManager.isDir(dirPath)){
fileValid = false;
this.addModalMessage(_appWrapper.appTranslations.translate('Chosen file directory is not a directory!'), 'error', []);
} else {
if (!_appWrapper.fileManager.isFileWritable(debugFileName)){
fileValid = false;
this.addModalMessage(_appWrapper.appTranslations.translate('Chosen file is not writable!'), 'error', []);
}
}
} else {
appState.modalData.currentModal.fileExists = true;
var filePath = path.resolve(debugFileName);
let dirPath = path.dirname(filePath);
if (!_appWrapper.fileManager.isFile(filePath)){
fileValid = false;
this.addModalMessage(_appWrapper.appTranslations.translate('Chosen file is not a file!'), 'error', []);
} else {
if (!_appWrapper.fileManager.fileExists(dirPath)){
fileValid = false;
this.addModalMessage(_appWrapper.appTranslations.translate('Chosen directory does not exist!'), 'error', []);
} else {
if (_appWrapper.fileManager.isDir(dirPath)){
if (!_appWrapper.fileManager.isFileWritable(filePath)){
fileValid = false;
this.addModalMessage(_appWrapper.appTranslations.translate('Chosen file is not writable!'), 'error', []);
}
} else {
fileValid = false;
this.addModalMessage(_appWrapper.appTranslations.translate('Chosen direcory it not a directory!'), 'error', []);
}
}
}
}
}
if (!fileValid){
appState.modalData.currentModal.fileExists = false;
appState.modalData.currentModal.confirmDisabled = true;
modalHelper.modalNotBusy();
} else {
modalHelper.setModalVar('file', debugFileName);
modalHelper.setModalVar('confirmDisabled', false);
modalHelper.modalNotBusy();
setTimeout(() => {
let buttonEl = appState.modalData.modalElement.querySelector('.modal-button-confirm');
if (buttonEl){
buttonEl.focus();
}
}, this.getConfig('shortPauseDuration'));
}
}
/**
* Clears all user messages
*
* @param {Event} e Event that triggered the method
* @return {undefined}
*/
clearUserMessages (e) {
if (e && e.preventDefault && _.isFunction(e.preventDefault)){
e.preventDefault();
}
appState.userMessageQueue = [];
appState.userMessages = [];
this.log('User messages cleared', 'info', []);
}
/**
* Changes minimum user message level for displaying user messages
*
* @param {Event} e Event that triggered the method
* @return {undefined}
*/
changeUserMessageLevel (e) {
var level = e.target.value;
_appWrapper.appConfig.setConfigVar('userMessages.userMessageLevel', level);
appState.userMessagesData.selectFocused = false;
}
/**
* Opens modal dialog with debugging configuration editor
*
* @return {undefined}
*/
openDebugConfigEditor () {
let modalHelper = _appWrapper.getHelper('modal');
let modalOptions = {
title: _appWrapper.appTranslations.translate('Debug config editor'),
confirmButtonText: _appWrapper.appTranslations.translate('Save'),
cancelButtonText: _appWrapper.appTranslations.translate('Cancel'),
};
appState.modalData.currentModal = modalHelper.getModalObject('debugConfigEditorModal', modalOptions);
modalHelper.modalBusy(_appWrapper.appTranslations.translate('Please wait...'));
_appWrapper._confirmModalAction = this.saveDebugConfig.bind(this);
_appWrapper._cancelModalAction = (evt) => {
if (evt && evt.preventDefault && _.isFunction(evt.preventDefault)){
evt.preventDefault();
}
// appState.status.noHandlingKeys = false;
modalHelper.modalNotBusy();
_appWrapper._cancelModalAction = _appWrapper.__cancelModalAction;
return _appWrapper.__cancelModalAction();
};
modalHelper.openCurrentModal();
}
/**
* Handler for saving debug configuration (from debug config modal)
*
* @async
* @param {Event} e Event that triggered the method
* @return {undefined}
*/
async saveDebugConfig (e) {
if (e && e.preventDefault && _.isFunction(e.preventDefault)){
e.preventDefault();
}
let modalHelper = _appWrapper.getHelper('modal');
let utilHelper = _appWrapper.getHelper('util');
var form = e.target;
let finalConfig = await utilHelper.setObjectValuesFromForm(form, appState.config);
await _appWrapper.appConfig.setConfig(finalConfig);
modalHelper.closeCurrentModal();
}
/**
* Returns number of debug messages that have stack information
*
* @return {Number} Number of debug messages with stack information
*/
getDebugMessageStacksCount () {
let stackCount = 0;
for(let i=0; i<appState.debugMessages.length; i++){
if (appState.debugMessages[i].stack && appState.debugMessages[i].stack.length){
stackCount++;
}
}
return stackCount;
}
/**
* Checks whether all debug stack messages are displayed
*
* @return {Boolean} False if all debug stack infos are displayed, true otherwise
*/
getDebugMessageStacksState () {
let stacksCount = this.getDebugMessageStacksCount();
let stacksOpen = 0;
for(let i=0; i<appState.debugMessages.length; i++){
if (appState.debugMessages[i].stack && appState.debugMessages[i].stack.length){
if (appState.debugMessages[i].stackVisible){
stacksOpen++;
}
}
}
return stacksOpen >= stacksCount;
}
/**
* Shows or hides stack info for all debug messages
*
* @return {undefined}
*/
toggleDebugMessageStacks () {
let currentState = !this.getDebugMessageStacksState();
for(let i=0; i<appState.debugMessages.length; i++){
if (appState.debugMessages[i].stack && appState.debugMessages[i].stack.length){
appState.debugMessages[i].stackVisible = currentState;
}
}
}
/**
* Expands or contracts app-debug part of the application
*
* @return {undefined}
*/
toggleDebugMessages () {
_appWrapper.appConfig.setConfigVar('debug.messagesExpanded', !this.getConfig('debug.messagesExpanded'));
}
/**
* Toggles usage data display flag on or off
*
* @async
* @return {undefined}
*/
async toggleUsageData () {
appState.config.debug.usage = !appState.config.debug.usage;
}
/**
* Starts app usage monitoring
*
* @async
* @return {undefined}
*/
async startUsageMonitor () {
await this.refreshUsageData();
this.intervals.usageData = setInterval(this.boundMethods.refreshUsageData, this.getConfig('debug.usageInterval', 1000));
}
/**
* Stops app usage monitoring
*
* @async
* @return {undefined}
*/
stopUsageMonitor () {
clearInterval(this.intervals.usageData);
appState.usageData.minCpu = -1;
appState.usageData.maxCpu = 1;
appState.usageData.minMemory = -1;
appState.usageData.maxMemory = 1;
}
/**
* Refreshes current usage data, setting max and min values if needed and pushing previous values to history
*
* @async
* @return {undefined}
*/
async refreshUsageData () {
let data = await this.getUsageData();
if (data){
if (appState.usageData.previous.length > this.getConfig('debug.usageHistoryCount', 1000)){
appState.usageData.previous = appState.usageData.previous.slice(1, 2);
}
if (appState.usageData.previous.length){
appState.usageData.change.cpu = _.round(data.cpu - appState.usageData.previous[appState.usageData.previous.length-1].cpu, 2);
appState.usageData.change.memory = data.memory - appState.usageData.previous[appState.usageData.previous.length-1].memory;
if (isNaN(appState.usageData.change.cpu)){
appState.usageData.change.cpu = 0;
}
if (isNaN(appState.usageData.change.memory)){
appState.usageData.change.memory = 0;
}
} else {
appState.usageData.change.cpu = 0;
appState.usageData.change.memory = 0;
}
appState.usageData.previous.push(appState.usageData.current);
appState.usageData.current = data;
if (data.cpu > appState.usageData.maxCpu){
appState.usageData.maxCpu = data.cpu;
} else if (appState.usageData.minCpu === -1 || data.cpu < appState.usageData.minCpu){
appState.usageData.minCpu = data.cpu;
}
if (data.memory > appState.usageData.maxMemory){
appState.usageData.maxMemory = data.memory;
} else if (appState.usageData.minMemory === -1 || data.memory < appState.usageData.minMemory){
appState.usageData.minMemory = data.memory;
}
}
}
/**
* Gets usage data from the OS
*
* @async
* @return {Object} Usage data object with 'cpu' and 'memory' properties
*/
async getUsageData () {
var returnPromise;
var resolveReference;
returnPromise = new Promise((resolve) => {
resolveReference = resolve;
});
pusage.stat(process.pid, (err, stat) => {
if (err){
this.log(err, 'error', []);
resolveReference(false);
} else {
stat.timestamp = +new Date();
resolveReference(stat);
}
});
return returnPromise;
}
/**
* Handler for usage interval change - stops current interval and starts new one with new duration
*
* @async
* @return {undefined}
*/
async usageIntervalChange (){
if (appState.config.debug.usage){
clearInterval(this.intervals.usageData);
this.intervals.usageData = setInterval(this.boundMethods.refreshUsageData, this.getConfig('debug.usageInterval', 1000));
}
}
/**
* Toggles display of usage graphs
*
* @return {undefined}
*/
toggleUsageGraphs (){
appState.config.debug.usageGraphs = !appState.config.debug.usageGraphs;
}
}
exports.DebugHelper = DebugHelper;