/*
* Copyright 2015 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This tool does display the messages sent to the server.
* @private
*/
Ext.define("Ametys.plugins.coreui.system.requesttracker.RequestTrackerTool", {
extend: "Ametys.tool.Tool",
statics: {
/**
* This action find the unique instance of the request tracker tool, and removes all the entries
*/
removeAll: function()
{
var tool = Ametys.tool.ToolsManager.getTool("uitool-requeststracker");
if (tool != null)
{
tool.tree.getRootNode().removeAll();
}
else
{
this.getLogger().error("Cannot remove entries from unexisting tool 'uitool-requeststracker'");
}
}
},
/**
* @property {Number} _currentId The current identifier. Each request displayed will show an identifier
* @private
*/
/**
* @property {Ext.data.TreeStore} store The store with the requests sent
* @private
*/
/**
* @property {Ext.tree.Panel} tree The tree panel displaying the requests
* @private
*/
constructor: function()
{
this.callParent(arguments);
this._currentId = 1;
Ametys.data.ServerComm._observer = this;
},
createPanel: function()
{
this.store = Ext.create("Ext.data.TreeStore",{
model: "Ametys.plugins.coreui.system.requesttracker.RequestTrackerTool.RequestEntry",
autoDestroy: true,
autoSync: true,
proxy: { type: 'memory' }
});
this.store.setRootNode({
type: 'root',
text: "Root",
label: "Root",
expanded: true
});
this.tree = Ext.create("Ext.tree.Panel", {
rootVisible: false,
minHeight: 60,
minWidth: 100,
flex: 0.5,
stateful: true,
stateId: this.self.getName() + "$treegrid",
store: this.store,
scrollable: true,
border: true,
defaults: {
width: 1000,
},
dockedItems: [
{
xtype: 'toolbar',
dock: 'top',
itemId: "toolbar",
layout: {
type: 'hbox',
align: 'stretch'
},
items: [
{
xtype: 'textfield',
cls: 'ametys',
itemId: 'textfilter',
flex: 1,
maxWidth: 30,
maxWidth: 300,
emptyText: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_TOOLBAR_FILTER}}",
listeners: {change: Ext.Function.createBuffered(this._filter, 500, this)},
style: {
marginRight: '0px'
}
},
{
// Clear filter
tooltip: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_TOOLBAR_CLEARFILTER}}",
handler: Ext.bind (this._clearFilter, this),
iconCls: 'a-btn-glyph ametysicon-eraser11 size-16',
cls: 'a-btn-light'
},
{
xtype: 'tbspacer',
width: 20
},
"{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_TOOLBAR_PRIORITY}}",
{
tooltip: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_SYNCHRONOUS}}",
handler: Ext.bind (this._filter, this),
itemId: 'priorityfiltersync',
iconCls: 'a-btn-glyph ametysicon-arrow-circle-right-double size-16',
cls: 'a-btn-light',
enableToggle: true,
pressed: true,
style: {
marginRight: '2px'
}
},
{
tooltip: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_LONG_REQUEST}}",
handler: Ext.bind (this._filter, this),
itemId: 'priorityfilterlong',
iconCls: 'a-btn-glyph ametysicon-datetime-clock size-16',
cls: 'a-btn-light',
enableToggle: true,
pressed: true,
style: {
marginRight: '2px'
}
},
{
tooltip: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_MAJOR}}",
handler: Ext.bind (this._filter, this),
itemId: 'priorityfiltermajor',
iconCls: 'a-btn-glyph ametysicon-arrow-up-double size-16',
cls: 'a-btn-light',
enableToggle: true,
pressed: true,
style: {
marginRight: '2px'
}
},
{
tooltip: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_NORMAL}}",
handler: Ext.bind (this._filter, this),
itemId: 'priorityfilternormal',
iconCls: 'a-btn-glyph ametysicon-arrow-up-simple size-16',
cls: 'a-btn-light',
enableToggle: true,
pressed: true,
style: {
marginRight: '2px'
}
},
{
tooltip: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_MINOR}}",
handler: Ext.bind (this._filter, this),
itemId: 'priorityfilterminor',
iconCls: 'a-btn-glyph ametysicon-arrow-down-simple size-16',
cls: 'a-btn-light',
enableToggle: true,
style: {
marginRight: '2px'
}
},
{
tooltip: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_IDLE}}",
handler: Ext.bind (this._filter, this),
itemId: 'priorityfilteridle',
iconCls: 'a-btn-glyph ametysicon-arrow-down-double size-16',
cls: 'a-btn-light',
enableToggle: true,
style: {
marginRight: '2px'
}
},
{
tooltip: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_BEACON}}",
handler: Ext.bind (this._filter, this),
itemId: 'priorityfilterbeacon',
iconCls: 'a-btn-glyph ametysicon-system-connection-wireless size-16',
cls: 'a-btn-light',
enableToggle: true
}, {
xtype: 'tbspacer',
flex: 0.0001
},
{
tooltip: "{{i18n PLUGINS_CORE_UI_TOOLS_REQUESTS_TRACKER_CLEAR_DESCRIPTION}}",
handler: function() { Ametys.plugins.coreui.system.requesttracker.RequestTrackerTool.removeAll(); },
iconCls: 'ametysicon-garbage11 size-16',
cls: 'a-btn-light'
}
]
}
],
columns: [
{stateId: 'treegrid-id', xtype: 'treecolumn', header: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_ID}}", minWidth: 102, width: 102, sortable: false, hideable: false, dataIndex: 'name', renderer: Ext.bind(this._renderId, this)},
{stateId: 'treegrid-date', header: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_TIME}}", minWidth: 65, width: 65, sortable: false, hideable: false, dataIndex: 'date', renderer: Ext.bind(this._renderDateOrPriority, this)},
{stateId: 'treegrid-duration', header: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_DURATION}}", minWidth: 115, width: 115, sortable: false, hideable: false, dataIndex: 'serverSideDuration', renderer: Ext.bind(this._renderDuration, this) },
{stateId: 'treegrid-status', header: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_STATUS}}", minWidth: 70, width: 70, sortable: false, dataIndex: 'status', hideable: false, renderer: Ext.bind(this._renderStatus, this)},
{stateId: 'treegrid-call', header: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_CALL}}", minWidth: 200, flex: 1000, sortable: false, hideable: false, dataIndex: 'type', renderer: Ext.bind(this._renderCall, this)},
],
viewConfig: {
getRowClass: function(record) {
if (record.get('type') == null)
{
return record.get('status') && record.get('status') != '200' ? 'request-errors' : '';
}
else if (record.get('errors') == 0 || record.get('status') == null)
{
return ''
}
else
{
return record.get('children').length == record.get('errors') ? 'request-errors' : 'request-warnings';
}
}
},
listeners: {
'selectionchange': this._onSelectionChange,
scope : this
}
});
this.rightRequestPanel = Ext.create("Ext.Component", {
scrollable: true,
title: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_DISPLAYMESSAGE_REQUEST}}",
cls: 'a-panel-text',
defaultHtml: "{{i18n PLUGINS_CORE_UI_TOOLS_REQUESTS_TRACKER_MESSAGE}}",
html: "{{i18n PLUGINS_CORE_UI_TOOLS_REQUESTS_TRACKER_MESSAGE}}"
});
this.rightResponsePanel = Ext.create("Ext.Component", {
scrollable: true,
title: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_DISPLAYMESSAGE_RESPONSE}}",
cls: 'a-panel-text',
defaultHtml: "{{i18n PLUGINS_CORE_UI_TOOLS_REQUESTS_TRACKER_MESSAGE}}",
html: "{{i18n PLUGINS_CORE_UI_TOOLS_REQUESTS_TRACKER_MESSAGE}}"
});
this.rightCallstackPanel = Ext.create("Ext.Component", {
scrollable: true,
title: "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_DISPLAYMESSAGE_CALLSTACK}}",
cls: 'a-panel-text',
defaultHtml: "{{i18n PLUGINS_CORE_UI_TOOLS_REQUESTS_TRACKER_MESSAGE}}",
html: "{{i18n PLUGINS_CORE_UI_TOOLS_REQUESTS_TRACKER_MESSAGE}}",
listeners: {
'show': function() {
this.openTrace();
},
scope: this
}
});
this.rightPanel = Ext.create("Ext.tab.Panel", {
stateId: this.self.getName() + "$rightPanel",
stateful: true,
minWidth: 100,
split: true,
border: true,
flex: 0.5,
items: [ this.rightRequestPanel, this.rightResponsePanel, this.rightCallstackPanel ]
});
this._messageRequestTpl = new Ext.Template(
"<b>{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_DISPLAYMESSAGE_URL}}</b> : ",
"{url}<br/><br/>",
"<b>{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_DISPLAYMESSAGE_PARAMETERS}}</b> : ",
"<code class='request-tracker'>{parameters}</code>",
);
this._messageResponseTpl = new Ext.Template(
"<code class='request-tracker'>{response}</code><br/>",
);
this._messageCallstackTpl = new Ext.XTemplate(
"<tpl if='unminified == false'><div class='request-tracker-unminifying'>{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_DISPLAYMESSAGE_CALLSTACK_LOADING}}</div></tpl>",
"<div>{callstack}</div>"
);
return Ext.create("Ext.container.Container", {
layout: {
type: 'hbox',
align: 'stretch'
},
cls: 'uitool-requesttracker',
items: [ this.tree, this.rightPanel ]
});
},
_onSelectionChange: function(view, records)
{
if(records.length > 0 && this._isMsg(records[0]))
{
this._onSelectMessage(view, records[0])
}
else
{
this.rightRequestPanel.update(this.rightRequestPanel.defaultHtml);
this.rightResponsePanel.update(this.rightResponsePanel.defaultHtml);
this.rightCallstackPanel.update(this.rightCallstackPanel.defaultHtml);
}
},
/**
* @private
* Is a message record (or a group message)?
*/
_isMsg: function(record)
{
return record.get('name').includes(".");
},
_renderId: function(value, metadata, record)
{
if (this._isMsg(record))
{
let i = value.indexOf(".");
return value.substring(i+1);
}
else
{
return value + " (" + record.get('children').length + ")";
}
},
_renderDateOrPriority: function(value, metadata, record)
{
if (this._isMsg(record))
{
let priority;
switch (record.get('message').priority)
{
case Ametys.data.ServerComm.PRIORITY_MAJOR: priority = "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_MAJOR}}"; break;
case Ametys.data.ServerComm.PRIORITY_NORMAL: priority = "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_NORMAL}}"; break;
case Ametys.data.ServerComm.PRIORITY_MINOR: priority = "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_MINOR}}"; break;
case Ametys.data.ServerComm.PRIORITY_SYNCHRONOUS: priority = "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_SYNCHRONOUS}}"; break;
case Ametys.data.ServerComm.PRIORITY_LONG_REQUEST: priority = "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_LONG_REQUEST}}"; break;
case Ametys.data.ServerComm.PRIORITY_BEACON: priority = "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_BEACON}}"; break;
case Ametys.data.ServerComm.PRIORITY_IDLE: priority = "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_PRIORITY_IDLE}}"; break;
default: priority = record.get('message').priority;
}
return priority;
}
else
{
return Ext.util.Format.dateRenderer(Ext.Date.patterns.ShortTime).apply(this, arguments);
}
},
_renderDuration: function(value, metadata, record)
{
if (isNaN(parseFloat(value)))
{
return "<div style='padding-top: 16px' class='x-mask-msg-text'></div>";
}
else if (!this._isMsg(record))
{
return "<div style='text-align: right'>" + Ext.util.Format.duration(record.get('globalDuration')*1000.0) + " / " + Ext.util.Format.duration(record.get('serverSideDuration')*1000.0) + "<div>";
}
else
{
return "<div style='text-align: right'>" + Ext.util.Format.duration(record.get('serverSideDuration')*1000.0) + "<div>";
}
},
_renderStatus: function(value, metadata, record)
{
if (this._isMsg(record))
{
return value;
}
else
{
switch (value)
{
case 0:
if (record.get('errors') > 0)
{
return Ext.String.format("{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_STATUS_ERRORS}}", record.get('errors'), record.get('children').length);
}
else
{
return "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_STATUS_OK}}";
}
case 1: return "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_STATUS_CANCELED}}";
case 2: return "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_STATUS_FAILURE}}";
default: return value;
}
}
},
/**
* @private
* The renderer for the call column
* @param {Object} value The data value for the current cell
* @param {Object} metaData A collection of metadata about the current cell; can be used or modified by the renderer. Recognized properties are: tdCls, tdAttr, and style.
* @param {Ext.data.Model} record The record for the current row
* @param {Number} rowIndex The index of the current row
* @param {Number} colIndex The index of the current column
* @param {Ext.data.Store} store The data store
* @param {Ext.view.View} view The current view
* @return {String} The HTML string to be rendered.
*/
_renderCall: function(value, metaData, record, rowIndex, colIndex, store, view)
{
if (this._isMsg(record))
{
return this._clientCallToString(record);
}
else
{
switch (value)
{
case "async": return "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_CALL_ASYNC}}";
case "sync": return "{{i18n PLUGINS_CORE_UI_REQUESTSTRACKER_TOOL_COL_CALL_SYNC}}";
}
}
},
/**
* @private
*/
_clientCallToString: function(record)
{
let cc = record.get('clientCall');
if (cc == null)
{
return (record.get('workspace') ? ("/_" + record.get('workspace')) : record.get('plugin') ? ('/plugins/' + record.get('plugin')) : '') + "/" +
record.get('url') + " (" + record.get('returnType') + ")";
}
else
{
return (cc.id || cc.role) + "#" + cc.method + (cc.id ? " (" + cc.role + ")": "");
}
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_NOSELECTION;
},
getType: function()
{
return Ametys.tool.Tool.TYPE_DEVELOPER;
},
/**
* Listener on the south grid panel (details in a request), when selecting a record
* @param {Ext.selection.RowModel} selModel The selection mode
* @param {Ext.data.Model} record The record selected
* @param {Object} eOpts The options object passed to Ext.util.Observable.addListener.
* @private
*/
_onSelectMessage: function (selModel, record, eOpts)
{
var id = (record.get('name')).split(".")[1];
var message = record.get("message");
var response = record.get("response");
var parametersAsString = Ext.JSON.prettyEncode( Ext.JSON.decode(Ext.JSON.encode(message.parameters || null)) ); // Encode/decode to ensure the json object reflect the sent one (e.g. eliminate "undefined" values)
var responseAsString = this._responseToString(response, id);
this.rightRequestPanel.update(this._messageRequestTpl.applyTemplate({url: message.url, parameters: parametersAsString}));
this.rightResponsePanel.update(this._messageResponseTpl.applyTemplate({response: responseAsString}));
this.rightCallstackPanel.update(this._messageCallstackTpl.applyTemplate({unminified: false, callstack: Ext.String.stacktraceToHTML(record.get("callstack"))}));
console.info(record.get("callstack"));
if (this.rightCallstackPanel.isVisible())
{
this.openTrace();
}
},
/**
* Display a part of the response as a readable string
* @param {Object} response The XMLHTTPResponse
* @param {String} id The id of the part of the response to extract and display
* @private
*/
_responseToString: function(response, id)
{
if (response == null)
{
return "null";
}
else
{
function xmlstringToHTML(xmlstring)
{
function escape(htmlstring)
{
return htmlstring.replace(/&/g, '&').replace(/</g, '<').replace(/\r\n/g, '<br/>').replace(/&#160;/g, ' ');
}
function escapeOpening(tagstring)
{
return "<span class='tag'>" + tagstring.replace(/( [^=]+)(=)(\"[^"]*\")/g, "<span class='attr-name'>$1</span><span class='attr-eq'>$2</span><span class='attr-value'>$3</span>") + "</span>";
}
function escapeText(textstring, pad)
{
var json = Ext.JSON.decode(textstring, true)
if (json != null)
{
var ppad = parseInt(pad/ 2) + 1;
textstring = Ext.JSON.prettyEncode(json, ppad);
if (textstring.indexOf("<br/>") != -1)
{
var padding = "";
for (var i = 0; i < ppad; i++)
{
padding += '    ';
}
textstring = "<br/>" + padding + textstring + "<br/>";
}
}
return "<span class='text'>" + textstring + "</span>";
}
function escapeClosing(tagstring)
{
return "<span class='tag'>" + tagstring + "</span>";
}
var formatted = '';
xml = xmlstring.replace(/(>)(<)(\/*)/g, '$1\r\n$2$3');
var pad = 0;
Ext.each(xml.split('\r\n'), function(node, index)
{
var indent = 0;
if (node.match( /.+<\/\w[^>]*>$/ ))
{
// full tag <test>foo</test>
node = escape(node);
var i = node.indexOf('>');
var j = node.indexOf('<', i+1);
node = escapeOpening(node.substring(0, i+1))
+ escapeText(node.substring(i+1, j), pad)
+ escapeClosing(node.substring(j));
indent = 0;
}
else if (node.match( /^<\/\w/ ))
{
// just a closing tag </test>
node = escape(node);
node = escapeClosing(node);
if (pad != 0)
{
pad -= 1;
}
}
else if (node.match( /^<\w[^>]*[^\/]>.*$/ ))
{
// just an opening tag <test attr="1">
node = escape(node);
node = escapeOpening(node);
indent = 1;
}
else
{
// autoclosing tags <test/>
node = escape(node);
node = escapeOpening(node);
indent = 0;
}
var padding = '';
for (var i = 0; i < pad; i++)
{
padding += '  ';
}
formatted += padding + node + '<br/>';
pad += indent;
}
);
return formatted;
}
var node = Ext.dom.Query.selectNode("/responses/response[@id='" + id + "']", response.responseXML);
if (node.outerHTML)
{
return xmlstringToHTML(node.outerHTML);
}
else
{
try
{
// Gecko- and Webkit-based browsers (Firefox, Chrome), Opera.
return xmlstringToHTML((new XMLSerializer()).serializeToString(node));
}
catch (e)
{
try
{
// Internet Explorer.
return xmlstringToHTML(xmlNode.xml);
}
catch (e) {
//Other browsers without XML Serializer
return "..."
}
}
}
}
},
_createEntry: function(type, messages)
{
var store = this.store;
try
{
var id = this._currentId++;
var record = Ext.create("Ametys.plugins.coreui.system.requesttracker.RequestTrackerTool.RequestEntry", {
"id": id,
"name": id,
"response": null,
"expanded": true,
"type": type,
"date": new Date(),
"globalDuration": null,
"serverSideDuration": null,
"status": null,
"iconCls": 'a-tree-glyph ametysicon-system-server-sync',
"children": this._createBottomLevelModelEntry(id, messages)
});
// Scroll to the top of the panel if is was already on top...
if (this.tree.getView().getEl().getScrollTop() == 0)
{
let me = this;
window.setTimeout(function() {
me.tree.getView().getEl().setScrollTop(0);
}, 0);
}
store.suspendEvents();
store.getRootNode().insertBefore(record, store.getRootNode().getChildAt(0));
this._filter();
store.resumeEvents();
this.tree.getView().refresh();
return id;
}
catch (e)
{
this.getLogger().error({
message: "Cannot create the request entry",
details: e
})
}
},
/**
* Create the second level nodes
* @param {String} id The parent id
* @param {Object[]} messages The messages that will create the sub nodes
* @private
*/
_createBottomLevelModelEntry: function (id, messages)
{
let rs = [];
for (let i = 0; i < messages.length; i++)
{
var message = messages[i];
let r = Ext.create("Ametys.plugins.coreui.system.requesttracker.RequestTrackerTool.RequestEntry", {
"name": id + "." + i,
"response": null,
"type": null,
"date": null,
"globalDuration": null,
"serverSideDuration": null,
"message": message,
"status": null,
"iconCls": 'a-tree-glyph ametysicon-desktop-envelope-open',
"leaf": true
});
rs.push(r);
}
return rs;
},
_updateEntry: function(observerId, responseType, response)
{
try
{
let record = this.store.byIdMap[observerId];
if (record == null)
{
// Let's ignore this request arrival, because we were not listening when it started
return;
}
this.store.suspendEvents();
// update child firt to reflect on parent values
this._updateBottomLevelModelEntry(record, response);
record.set({
"response": response,
"globalDuration": (new Date().getTime() - record.get("date").getTime()) / 1000.0,
"serverSideDuration": response && response.responseXML ? Ext.dom.Query.selectNode("/responses/times", response.responseXML).getAttribute("duration") / 1000.0 : Infinity,
"status": responseType
}, { commit: true });
this.store.resumeEvents();
this.tree.getView().refresh();
// If the current selection was just updated, let's refresh the right panel
let selection = this.tree.getSelectionModel().getSelection();
if (selection.length > 0 && this._isMsg(selection[0]) && selection[0].parentNode.id == record.id)
{
this._onSelectionChange(this.tree.getView(), selection);
}
}
catch (e)
{
this.getLogger().error({
message: "Cannot update the request entry",
details: e
})
}
},
_updateBottomLevelModelEntry: function (record, response)
{
let children = record.get('children');
for (let i = 0; i < children.length; i++)
{
let subRecord = children[i];
var responseElement = response && response.responseXML ? Ext.dom.Query.selectNode("/responses/response[@id='" + i + "']", response.responseXML) : null;
subRecord.set({
"response": response,
"serverSideDuration": response && response.responseXML ? Ext.dom.Query.selectNode("/responses/times/time[@id='" + i + "']", response.responseXML).getAttribute("duration") / 1000.0 : Infinity,
"status": responseElement ? responseElement.getAttribute("code") : "?"
}, { commit: true });
}
},
/**
* Observer of the Ametys.data.ServerComm for sent request
* @param {Object} sendOptions The options used in Ametys.data.ServerComm#_sendMessages
*/
onRequestDeparture: function (sendOptions)
{
sendOptions.observerId = this._createEntry("async", sendOptions.messages);
},
/**
* Observer of the Ametys.data.ServerComm for request back
* @param {Object} sendOptions The options used in Ametys.data.ServerComm#_sendMessages
* @param {Number} responseType 0 for success, 1 for canceled and 2 for failure
* @param {Object} [response] The XMLHTTPResponse when available
*/
onRequestArrival: function (sendOptions, responseType, response)
{
this._updateEntry(sendOptions.observerId, responseType, response);
},
/**
* Observer of the Ametys.data.ServerComm for sent synchronous request
* @param {Object} message The message sent. Argument of Ametys.data.ServerComm#_sendSynchronousMessage
*/
onSyncRequestDeparture: function (message)
{
let me = this;
window.setTimeout(function() {
message.observerId = me._createEntry("sync", [message]);
});
},
/**
* Observer of the Ametys.data.ServerComm back from a synchronous request
* @param {Object} message The message sent. Argument of Ametys.data.ServerComm#_sendSynchronousMessage
* @param {Number} responseType 0 for success and 2 for failure
* @param {Object} [response] The XMLHTTPResponse when available
*/
onSyncRequestArrival: function (message, responseType, response)
{
// We asynchronously update the grid, otherwise this can lead to side effect (especially if the request is done while rendering a cell in a grid RUNTIME-3980)
let me = this;
window.setTimeout(function() {
me._updateEntry(message.observerId, responseType, response);
});
},
onClose: function ()
{
this.callParent(arguments);
Ametys.data.ServerComm._observer = {};
},
/**
* Load and display the stacktrace of the currently selected request
* @param {Object} span The dom element that triggered the open trace
*/
openTrace: function ()
{
let selection = this.tree.getSelectionModel().getSelection();
if (selection.length > 0)
{
let record = selection[0];
let callstack = record.get("callstack");
if (callstack)
{
Ametys.plugins.coreui.system.devmode.StacktraceHelper.unminifyStacktrace(callstack, Ext.bind(this._openTraceCb, this, [record.getId()], true));
}
}
},
/**
* Callback after unminifying the stacktrace
* @param {String} response The unminified callstack, or null
* @param {String} recordId The id for which the unminify was asked
*/
_openTraceCb: function (response, recordId)
{
if (recordId == this.tree.getSelectionModel().getSelection()[0].getId())
{
this.rightCallstackPanel.update(this._messageCallstackTpl.applyTemplate({unminified: true, callstack: Ext.String.stacktraceToHTML(response, 1)}));
}
},
_clearFilter: function()
{
let toolbar = this.tree.getDockedComponent("toolbar");
toolbar.getComponent("textfilter").reset();
},
_filter: function()
{
let me = this;
let toolbar = this.tree.getDockedComponent("toolbar");
let textFilter = new String(toolbar.getComponent("textfilter").getValue()).trim().toLowerCase();
let syncFilter = toolbar.getComponent("priorityfiltersync").pressed
let longFilter = toolbar.getComponent("priorityfilterlong").pressed
let majorFilter = toolbar.getComponent("priorityfiltermajor").pressed
let normalFilter = toolbar.getComponent("priorityfilternormal").pressed
let minorFilter = toolbar.getComponent("priorityfilterminor").pressed
let idleFilter = toolbar.getComponent("priorityfilteridle").pressed
let beaconFilter = toolbar.getComponent("priorityfilterbeacon").pressed
this.tree.getStore().clearFilter();
if (textFilter || !syncFilter || !longFilter || !majorFilter || !minorFilter || !idleFilter || !beaconFilter)
{
this.tree.getStore().filterBy(filter);
}
function filter(record)
{
let isVisible = false;
if (me._isMsg(record))
{
if (!syncFilter && record.get('message').priority == Ametys.data.ServerComm.PRIORITY_SYNCHRONOUS
|| !longFilter && record.get('message').priority== Ametys.data.ServerComm.PRIORITY_LONG_REQUEST
|| !majorFilter && record.get('message').priority== Ametys.data.ServerComm.PRIORITY_MAJOR
|| !normalFilter && record.get('message').priority== Ametys.data.ServerComm.PRIORITY_NORMAL
|| !minorFilter && record.get('message').priority== Ametys.data.ServerComm.PRIORITY_MINOR
|| !idleFilter && record.get('message').priority== Ametys.data.ServerComm.PRIORITY_IDLE
|| !beaconFilter && record.get('message').priority== Ametys.data.ServerComm.PRIORITY_BEACON)
{
isVisible = false;
}
else
{
isVisible = me._clientCallToString(record).toLowerCase().includes(textFilter)
|| Ext.JSON.encode(record.get('message').parameters || '').toLowerCase().includes(textFilter);
}
}
else
{
for (let i = 0; !isVisible && i < record.childNodes.length; i++)
{
isVisible = filter(record.childNodes[i]);
}
if (isVisible && textFilter)
{
// me.tree.expandNode(record);
}
}
return isVisible;
}
}
});