/*
* Copyright 2019 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 displays a list of caches
*/
Ext.define('Ametys.plugins.admin.cache.CachesTool', {
extend: 'Ametys.tool.Tool',
/**
* @private
* @property {Ext.grid.Panel} _cachesGrid The caches grid
*/
constructor: function (config)
{
this.callParent(arguments);
Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onMessageModified, this);
},
getMBSelectionInteraction: function ()
{
return Ametys.tool.Tool.MB_TYPE_ACTIVE;
},
createPanel: function ()
{
this._cachesGrid = this._drawCachesPanel();
return this._cachesGrid;
},
setParams: function (params)
{
this.callParent(arguments);
this.refresh();
},
/**
* Refreshes the tool
*/
refresh: function ()
{
this.showRefreshing();
this._cachesGrid.getStore().load({ callback: this.showRefreshed, scope: this });
},
/**
* @private
* Draw the panel displaying the caches
*/
_drawCachesPanel: function ()
{
var store = Ext.create('Ext.data.Store', {
model: 'Ametys.plugins.admin.tool.CachesTool.Cache',
proxy: {
type: 'ametys',
role: 'org.ametys.core.cache.CacheManager',
methodName: 'getCachesAsJSONMap',
methodArguments: [],
reader: {
type: 'json'
}
},
groupField: 'type',
sorters: ['label']
});
return Ext.create('Ext.grid.Panel', {
region: 'center',
stateful: true,
stateId: this.self.getName() + "$grid",
store: store,
// Filter box
dockedItems: [{
dock: 'top',
xtype: 'toolbar',
layout: {
type: 'hbox',
align: 'stretch'
},
border: false,
defaults : {
cls: 'ametys',
labelWidth: 55,
labelSeparator: ''
},
items: [{
// Search input
xtype: 'textfield',
itemId: 'search-filter-input',
cls: 'ametys',
flex: 1,
maxWidth: 300,
emptyText: "{{i18n PLUGINS_ADMIN_TOOL_CLEAR_CACHE_FILTER_EMPTY_TEXT}}",
enableKeyEvents: true,
msgTarget: 'qtip',
listeners: {change: Ext.Function.createBuffered(this._filterByTitle, 300, this)}
}, {
// Clear filter
tooltip: "{{i18n PLUGINS_ADMIN_TOOL_CLEAR_CACHE_FILTER_CLEAR}}",
handler: Ext.bind (this._clearFilter, this),
iconCls: 'a-btn-glyph ametysicon-eraser11 size-16',
cls: 'a-btn-light'
}]
}],
// Grouping by cache type
features: [
{
ftype: 'grouping',
enableGroupingMenu: false,
groupHeaderTpl: [
'{name:this.description}',
{
description: function(name) {
switch (name) {
case "REQUEST":
return "{{i18n PLUGINS_ADMIN_TOOL_REQUEST_CACHE}}";
case "MEMORY":
default:
return "{{i18n PLUGINS_ADMIN_TOOL_MEMORY_CACHE}}";
}
}
}
]
}
],
selModel: {
mode: 'MULTI'
},
columns: [
{ stateId: 'grid-id', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_ID}}", hidden: true, sortable: true, flex: 1, dataIndex: 'id' },
{ stateId: 'grid-label', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_NAME}}", sortable: true, flex: 1, dataIndex: 'label' },
{ stateId: 'grid-description', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_DESCRIPTION}}", sortable: true, flex: 3, dataIndex: 'description', renderer: this._renderWithTooltip },
{ stateId: 'grid-access', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_ACCESS}}", sortable: true, width: 140, dataIndex: 'access' },
{ stateId: 'grid-hit', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_HIT}}", hidden: true, sortable: true, width: 100, dataIndex: 'hit' },
{ stateId: 'grid-hit-rate', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_HIT_RATE}}", sortable: true, width: 100, dataIndex: 'hitRate', renderer: this._renderPercent },
{ stateId: 'grid-miss', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_MISS}}", hidden: true, sortable: true, width: 100, dataIndex: 'miss' },
{ stateId: 'grid-miss-rate', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_MISS_RATE}}", sortable: true, width: 100, dataIndex: 'missRate', renderer: this._renderPercent },
{ stateId: 'grid-nb-elements', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_NB_ELEMENTS}}", sortable: true, width: 140, dataIndex: 'nbElement', renderer: this._renderIfMemory },
{ stateId: 'grid-nb-evictions', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_NB_EVICTIONS}}", hidden: true, sortable: true, width: 140, dataIndex: 'nbEviction' },
{ stateId: 'grid-current-size', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_CURRENT_SIZE}}", sortable: true, width: 140, dataIndex: 'complexCurrentSize', renderer: this._renderComplexSize },
{ stateId: 'grid-max-size', header: "{{i18n PLUGINS_ADMIN_TOOL_CACHE_COL_MAX_SIZE}}", hidden: true, sortable: true, width: 140, dataIndex: 'complexMaxSize', renderer: this._renderComplexSize }
],
listeners: {
'selectionchange': Ext.bind(this.sendCurrentSelection, this)
}
})
},
/**
* @private
* Filters queries by input field value.
* @param {Ext.form.Field} field The field
*/
_filterByTitle: function (field)
{
var value = Ext.String.trim(field.getValue());
var regexFilter = new RegExp(value, 'i');
var currentSelection = this.getContentPanel().getSelection();
this.getContentPanel().getStore().clearFilter();
this.getContentPanel().getStore().filterBy(function(record){
return regexFilter.test(record.data.label) || regexFilter.test(record.data.description);
});
if (currentSelection.length > 0)
{
var me = this;
Ext.Array.each(currentSelection, function (sel) {
if (me.getContentPanel().getStore().findExact(me.getContentPanel().getStore().getModel().idProperty, sel.getId()) == -1)
{
// The current selection is not visible, clear the selection
me.getContentPanel().getSelectionModel().deselect([sel]);
}
})
}
},
/**
* Clear the current filter
*/
_clearFilter: function()
{
this.getContentPanel().down("#search-filter-input").reset();
this.getContentPanel().getStore().clearFilter();
},
/**
* Renders a value, editing the metadata to display the value as tooltip.
* @param {Object} value The data value for the current cell.
* @param {Object} metadata A collection of metadata about the current cell.
* @private
*/
_renderWithTooltip: function (value, metadata)
{
metadata.tdAttr = 'data-qtip="' + value + '"';
return value;
},
/**
* Renders a percentage value.
* If there is no hit/miss, display " - " instead, as a percentage wouldn't make sense
* @param {Object} value The data value for the current cell.
* @param {Object} metadata A collection of metadata about the current cell.
* @param {Object} record The record for the current row.
* @private
*/
_renderIfMemory: function (value, metadata, record)
{
if (record.get('type') == "MEMORY")
{
return value;
}
else
{
return "{{i18n PLUGINS_ADMIN_TOOL_CACHE_SIZE_NOT_AVAILABLE}}";
}
},
/**
* Renders a percentage value.
* If there is no hit/miss, display " - " instead, as a percentage wouldn't make sense
* @param {Object} value The data value for the current cell.
* @param {Object} metadata A collection of metadata about the current cell.
* @param {Object} record The record for the current row.
* @private
*/
_renderPercent: function (value, metadata, record)
{
return (record.get('hit') != 0 || record.get('miss') != 0) ? Ext.util.Format.number(value * 100, '0.## %') : " - ";
},
/**
* Renders a value in bytes, sensible to i18n.
* The size may not be computable, in this case, a specific text is displayed
* If size is equal to Java Long.MaxValue (2^63-1), return a text to tell infinite size is reached
* @param {Object} complexSize The data value for the current cell.
* @param {Boolean} complexSize.computableSize true if the size of the cache can be computed
* @param {Boolean} complexSize.size the size of the cache in bytes
* @private
*/
_renderComplexSize: function (complexSize)
{
if (complexSize.computableSize)
{
if (complexSize.size == 9223372036854775807)
{
return " - ";
}
else
{
return Ext.util.Format.fileSize(complexSize.size);
}
}
else
{
return "{{i18n PLUGINS_ADMIN_TOOL_CACHE_SIZE_NOT_AVAILABLE}}";
}
},
/**
* Fires a event of selection on message bus, from the selected contents in the grid.
* @protected
*/
sendCurrentSelection: function ()
{
var selection = this._cachesGrid.getSelectionModel().getSelection();
var targets = [];
for (var i = 0; i < selection.length; i++)
{
targets.push({
id: Ametys.message.MessageTarget.CACHE,
parameters: {
id: selection[i].id,
type: selection[i].data.type
}
});
}
Ext.create('Ametys.message.Message', {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: targets
});
},
/**
* Listener on creation or edition message.
* @param {Ametys.message.Message} message The edition message.
* @private
*/
_onMessageModified: function (message)
{
var targets = message.getTargets(Ametys.message.MessageTarget.CACHE);
if (targets.length > 0)
{
this.showOutOfDate();
}
},
});
Ext.define('Ametys.plugins.admin.tool.CachesTool.Cache', {
extend: 'Ext.data.Model',
fields: [
{ name: 'id' },
{ name: 'label' },
{ name: 'description' },
{ name: 'computableSize' },
{ name: 'access' },
{ name: 'hit' },
{ name: 'hitRate' },
{ name: 'miss' },
{ name: 'missRate' },
{ name: 'nbElement' },
{ name: 'nbEviction' },
{ name: 'type' },
{ name: 'currentSize' },
{ name: 'maxSize' },
{
name: 'complexCurrentSize',
calculate: function (data)
{
return {
computableSize: data.computableSize,
size: data.currentSize
};
},
sortType: function (value)
{
return value.computableSize ? value.size : -1;
}
},
{
name: 'complexMaxSize',
calculate: function (data)
{
return {
computableSize: data.computableSize,
size: data.maxSize
};
},
sortType: function (value)
{
return value.computableSize ? value.size : -1;
}
},
]
});
Ext.define("Ametys.message.CacheMessageTarget", {
override: "Ametys.message.MessageTarget",
statics:
{
/**
* @member Ametys.message.MessageTarget
* @readonly
* @property {String} CACHE The target of the message is the cache The expected parameters are:
* @property {String} CACHE.id The id of the cache
* @property {String} CACHE.type The type of the cache
*/
CACHE: "cache"
}
});