-
Piotr Gawron authoredPiotr Gawron authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
GuiConnector.js 17.32 KiB
"use strict";
var Promise = require("bluebird");
var logger = require('./logger');
var Functions = require('./Functions');
var SecurityError = require('./SecurityError');
var ValidationError = require('./ValidationError');
/**
* This static global object contains set of functions that returns/set data in
* the Gui (html).
*/
function GuiConnector() {
// X coordinate of the mouse in a browser.
//@type {number}
this.xPos = 0;
// coordinate of the mouse in a browser.
//@type {number}
this.yPos = 0;
this.getParams = [];
}
/**
*
* @param {GuiConnector} object
* @returns {GuiConnector}
*/
function returnThisOrSingleton(object) {
if (object === undefined || object === null) {
return GuiConnector.singleton;
} else {
return object;
}
}
/**
* List of GET params passed via url.
*/
GuiConnector.prototype.init = function () {
var self = returnThisOrSingleton(this);
if (!String.prototype.endsWith) {
String.prototype.endsWith = function (pattern) {
var d = this.length - pattern.length;
return d >= 0 && this.lastIndexOf(pattern) === d;
};
}
// noinspection PointlessBooleanExpressionJS,JSUnresolvedVariable
var isIE = /* @cc_on!@ */false || !!document.documentMode;
if (isIE) {
alert("This web page works well with Chrome, Firefox and Safari.");
}
// bootstrap tab initialization
$("ul.nav-tabs a").click(function (e) {
e.preventDefault();
$(this).tab('show');
});
self.getParams = [];
// find GuiConnector.getParams
window.location.search.replace(/\??(?:([^=]+)=([^&]*)&?)/g, function () {
function decode(s) {
return decodeURIComponent(s.split("+").join(" "));
}
self.getParams[decode(arguments[1])] = decode(arguments[2]);
});
self._touchStartEvent = function (e) {
if (e.originalEvent !== undefined) {
self.updateMouseCoordinates(e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY);
}
};
self._touchMoveEvent = function (e) {
if (e.originalEvent !== undefined) {
self.updateMouseCoordinates(e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY);
}
};
// force browser to update mouse coordinates whenever mouse move
jQuery(document).ready(function () {
$(document).mousemove(function (e) {
self.updateMouseCoordinates(e.pageX, e.pageY);
});
$(document).on('touchstart', self._touchStartEvent);
$(document).on('touchmove', self._touchMoveEvent);
});
if (self._windowResizeEvents === undefined) {
self._windowResizeEvents = [];
if (window.onresize !== null && window.onresize !== undefined) {
self.addWindowResizeEvent(window.onresize);
}
window.onresize = function () {
for (var i = 0; i < self._windowResizeEvents.length; i++) {
self._windowResizeEvents[i]();
}
};
}
newUrl = "";
//sorting of datatable column by input value https://stackoverflow.com/a/29221907/1127920
$.fn.dataTable.ext.order['dom-input'] = function (settings, col) {
return this.api().column(col, {order: 'index'}).nodes().map(function (td, i) {
return $('input', td).val();
});
}
};
var newUrl = "";
setInterval(function () {
if (global.window !== undefined && newUrl !== "") {
if (!global.window.location.href.endsWith(newUrl)) {
global.window.history.replaceState(null, null, newUrl);
}
}
}, 250);
/**
*
* @param {string} key
* @param {string} value
*/
GuiConnector.prototype.setUrlParam = function (key, value) {
var self = this;
if (value === null || value === "") {
value = undefined;
}
if (self.getParams[key] !== value) {
self.getParams[key] = value;
var url = window.location.pathname + '?';
for (var getParamKey in self.getParams) {
if (self.getParams.hasOwnProperty(getParamKey)) {
var getParamValue = self.getParams[getParamKey];
if (getParamValue !== undefined) {
url += getParamKey + "=" + getParamValue + "&";
}
}
}
newUrl = url;
}
};
/**
*
* @param {function} handler
*/
GuiConnector.prototype.addWindowResizeEvent = function (handler) {
this._windowResizeEvents.push(handler);
};
/**
*
* @param {function} handler
*/
GuiConnector.prototype.removeWindowResizeEvent = function (handler) {
var events = this._windowResizeEvents;
var index = events.indexOf(handler);
if (index > -1) {
events.splice(index, 1);
} else {
logger.warn("Cannot find listener", handler);
}
};
/**
* Returns name of the file with LCSB logo.
*
* @param bigLogo
* {@link Boolean} value determining if we want to have big logo or
* small one
* @returns {string}
*/
GuiConnector.prototype.getLcsbLogoImg = function (bigLogo) {
if (bigLogo) {
return 'lcsb_logo_mid.png';
} else {
return 'lcsb_logo.png';
}
};
/**
* Returns name of the file with image that should be presented when we are
* waiting for data to be loaded.
* @returns {string}
*/
GuiConnector.prototype.getLoadingImg = function () {
return "icons/ajax-loader.gif";
};
/**
*
* @returns {string}
*/
GuiConnector.prototype.getEmptyTileUrl = function () {
return "resources/images/empty_tile.png";
};
/**
* Returns home directory for images in the application.
* @returns {string}
*/
GuiConnector.prototype.getImgPrefix = function () {
return "resources/images/";
};
/**
* Updates coordinates of the mouse in the browser.
*
* @param {number} x
* @param {number} y
*/
GuiConnector.prototype.updateMouseCoordinates = function (x, y) {
var self = returnThisOrSingleton(this);
self.xPos = x;
self.yPos = y;
};
/**
*
* @param {string} [messageText]
*/
GuiConnector.prototype.showProcessing = function (messageText) {
var self = returnThisOrSingleton(this);
if (self._processingDialog === undefined) {
self._processingDialog = document.createElement("div");
self._processingDialogContent = document.createElement("div");
self._processingDialog.appendChild(self._processingDialogContent);
document.body.appendChild(self._processingDialog);
$(self._processingDialog).dialog({
modal: true,
title: "PROCESSING",
width: "150px",
closeOnEscape: false,
dialogClass: 'minerva-no-close'
});
}
if (messageText === undefined) {
messageText = "PROCESSING";
}
var messageImg = Functions.createElement({
type: "img",
src: 'resources/images/icons/ajax-loader.gif'
});
self._processingDialogContent.innerHTML = "";
self._processingDialogContent.style.textAlign = "center";
self._processingDialogContent.appendChild(messageImg);
$(self._processingDialog).dialog("option", "title", messageText);
$(self._processingDialog).dialog("open");
};
/**
*
*/
GuiConnector.prototype.hideProcessing = function () {
var self = returnThisOrSingleton(this);
$(self._processingDialog).dialog("close");
};
GuiConnector.prototype.showErrorDialog = function (title, content) {
var dialog = document.createElement('div');
dialog.title = title;
var dialogBody = document.createElement('p');
dialogBody.innerHTML = content;
dialog.appendChild(dialogBody);
$(dialog).dialog({
modal: true,
dialogClass: 'minerva-error-dialog',
classes: {
"ui-dialog": "ui-state-error"
},
close: function () {
$(this).dialog('destroy').remove();
}
}).siblings('.ui-dialog-titlebar').css("background", "red");
};
GuiConnector.prototype.showSuccessDialog = function (title, content) {
var dialog = document.createElement('div');
dialog.title = title;
var dialogBody = document.createElement('p');
dialogBody.innerHTML = content;
dialog.appendChild(dialogBody);
$(dialog).dialog({
dialogClass: 'minerva-success-dialog',
modal: true,
close: function () {
$(this).dialog('destroy').remove();
}
}).siblings('.ui-dialog-titlebar').css("background", "green");
};
/**
* Gather information that are presented to the user before submission to MinervaNet.
*
* @param {string|Error} error The error that triggered the report sequence.
* @return {Promise}
*/
GuiConnector.prototype.gatherReportData = function () {
return ServerConnector.getLoggedUser().then(function (user) {
return {
url: {
value: window.location.href,
tooltip: 'The error location. This information can help to narrow down the error source.'
},
login: {
value: user.getLogin(),
tooltip: 'Your account name. This information is useful in case the issue is specific to a certain account.'
},
email: {
value: user.getEmail(),
tooltip: 'Your contact email. If provided we might contact you for additional information.'
},
browser: {
value: navigator.userAgent,
tooltip: 'Your browser user agent. Many issues are specific to certain browsers. This information is important to identify those.'
},
timestamp: {
value: Math.floor(+new Date() / 1000),
tooltip: 'The error time. This information is useful to link the issue to a specific event on the server.'
} // TODO: Submission time rather than server time for now
};
});
};
/**
*
* @param {string|Error} error
* @param {boolean} [redirectIfSecurityError]
*/
GuiConnector.prototype.alert = function (error, redirectIfSecurityError) {
error = error || '';
if (redirectIfSecurityError && error instanceof SecurityError && ServerConnector.getSessionData().getLogin() === "anonymous") {
window.location.href = ServerConnector.getServerBaseUrl() + "login.xhtml?from=" + encodeURI(window.location.href);
} else {
var self = returnThisOrSingleton(this);
logger.error(error);
var errorData = self.getErrorMessageForError(error);
if (!errorData.showReport) {
self.showErrorDialog("An error occurred!", errorData.message);
} else {
self._errorDialog = document.createElement('div');
self._errorDialog.innerHTML = '<span class="ui-icon ui-icon-info" style="float: right;" title="The error message. This might not be human readable. If this issue persists you should should contact your administrator."></span>' +
'<span>' + errorData.message + '</span>';
self.gatherReportData().then(function (data) {
self._errorDialog.innerHTML += '<p class="report-dialog-warning">If you agree to submit the following information to the Minerva maintainers please uncheck all boxes that might contain sensitive data.</p>';
self._errorDialogData = document.createElement('div');
self._errorDialog.appendChild(self._errorDialogData);
self._errorDialogData.innerHTML += '<textarea id="report-comment" maxlength="255" placeholder="Add comment..."></textarea>';
Object.keys(data).forEach(function (key) {
self._errorDialogData.innerHTML += '<label>' +
(key === 'timestamp' ? new Date(data[key].value * 1000) : data[key].value) +
'<input class="report-check" type="checkbox" data-key="' + key + '" data-value="' + data[key].value + '"/>' +
'<span class="ui-icon ui-icon-info" title="' + data[key].tooltip + '"></span>' +
'</label>';
});
self._errorDialogData.innerHTML += '<div id="report-stacktrace">' +
'<h3>Stacktrace' +
'<span class="ui-icon ui-icon-info" title="' + 'The error stacktrace. The sequence of events that triggered this particular error.' + '"></span>' +
'</h3>' +
'<div><p>' + errorData.stacktrace + '</p></div>' +
'</div>';
$('#report-stacktrace')
.accordion({active: false, collapsible: true});
$('.report-check')
.checkboxradio()
.prop('checked', true)
.button('refresh');
$(self._errorDialogData)
.controlgroup({direction: 'vertical'});
$(self._errorDialog)
.tooltip({
classes: {
"ui-tooltip": "report-tooltip ui-corner-all ui-widget-shadow"
},
track: true,
position: {
my: 'right',
at: 'left'
}
});
});
document.body.appendChild(self._errorDialog);
$(self._errorDialog).dialog({
classes: {
'ui-dialog': 'report-dialog ui-corner-all',
'ui-dialog-titlebar': 'ui-corner-all'
},
title: 'An error occurred!',
resizable: true,
height: 'auto',
width: '500px',
modal: true,
close: function () {
$(this).dialog('destroy').remove();
},
buttons: {
'Submit': function () {
var report = {
stacktrace: errorData.stacktrace,
comment: $('#report-comment').val()
};
$('.report-check').each(function () {
var check = $(this);
if (check.is(':checked')) {
report[check.attr('data-key')] = check.attr('data-value');
}
});
ServerConnector.submitErrorToMinervaNet(report, function (error, response) {
if (error || response.statusCode !== 200) {
self.showErrorDialog('Report could not be submitted!',
'Please contact your system administrator if this issue persists.');
} else {
self.showSuccessDialog('Report has been submitted!',
'Thank you very much for helping us to improve Minerva.');
}
});
$(this).dialog('destroy').remove();
},
'Cancel': function () {
$(this).dialog('destroy').remove();
}
}
}).siblings('.ui-dialog-titlebar').css("background", "red");
}
}
};
/**
*
* @param {Error|string} error
* @returns {Object}
*/
GuiConnector.prototype.getErrorMessageForError = function (error) {
var expectedError = typeof error === 'string' || error instanceof SecurityError || error instanceof ValidationError;
var errorData = {
showReport: !expectedError,
message: typeof error === 'string' ? error : error.message,
stacktrace: error.stack
};
if (error instanceof SecurityError) {
if (ServerConnector.getSessionData().getLogin() === "anonymous") {
errorData.message = "<p>Please <a href=\"login.xhtml?from=" + encodeURI(window.location.href) + "\">login</a> to access this resource</p>";
} else {
errorData.message += "<p>Please <a href=\"login.xhtml?from=" + encodeURI(window.location.href) + "\">login</a> " + "as a different user or ask your administrator to change the permissions to access this resource.</p>";
}
}
return errorData;
};
/**
*
* @param {string} message
*/
GuiConnector.prototype.info = function (message) {
var self = returnThisOrSingleton(this);
if (self._infoDialog === undefined) {
self._infoDialog = document.createElement("div");
self._infoDialogContent = document.createElement("div");
self._infoDialog.appendChild(self._infoDialogContent);
document.body.appendChild(self._infoDialog);
$(self._infoDialog).dialog({
dialogClass: 'minerva-info-dialog',
classes: {
"ui-dialog": "ui-state-info"
},
modal: true,
title: "INFO"
});
}
self._infoDialogContent.innerHTML = message;
$(self._infoDialog).dialog("open");
};
/**
*
* @param {{message:string, [title]:string}} params
*/
GuiConnector.prototype.showConfirmationDialog = function (params) {
var message = params.message;
var title = params.title;
if (title === undefined) {
title = "Confirm";
}
return new Promise(function (resolve) {
$('<div></div>').appendTo('body')
.html('<div><h6>' + message + '</h6></div>')
.dialog({
dialogClass: 'minerva-confirmation-dialog',
modal: true, title: title, zIndex: 10000, autoOpen: true,
width: 'auto', resizable: false,
buttons: {
Yes: function () {
$(this).dialog("close");
resolve(true);
},
No: function () {
$(this).dialog("close");
resolve(false);
}
},
close: function (event, ui) {
$(this).remove();
}
});
});
};
/**
*
*/
GuiConnector.prototype.destroy = function () {
var self = returnThisOrSingleton(this);
if (self._infoDialog !== undefined) {
$(self._infoDialog).dialog("destroy").remove();
}
if (self._warnDialog !== undefined) {
$(self._warnDialog).dialog("destroy").remove();
self._warnDialog = undefined;
}
if (self._processingDialog !== undefined) {
$(self._processingDialog).dialog("destroy").remove();
}
if (self._errorDialog !== undefined) {
$(self._errorDialog).dialog("destroy").remove();
}
self._windowResizeEvents = undefined;
$(document).off('touchstart', self._touchStartEvent);
$(document).off('touchmove', self._touchMoveEvent);
};
/**
*
* @param {string} message
*/
GuiConnector.prototype.warn = function (message) {
var self = returnThisOrSingleton(this);
logger.warn(message);
if (self._warnDialog === undefined) {
self._warnDialog = document.createElement("div");
self._warnDialogContent = document.createElement("div");
self._warnDialog.appendChild(self._warnDialogContent);
document.body.appendChild(self._warnDialog);
$(self._warnDialog).dialog({
dialogClass: 'minerva-warn-dialog',
classes: {
"ui-dialog": "ui-state-highlight"
},
modal: true,
title: "WARNING"
});
}
self._warnDialogContent.innerHTML = message;
$(self._warnDialog).dialog("open");
};
GuiConnector.singleton = new GuiConnector();
module.exports = GuiConnector.singleton;