mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Adding initial version for showing statistics for the server, and
database nodes.
This commit is contained in:
37
web/pgadmin/misc/statistics/__init__.py
Normal file
37
web/pgadmin/misc/statistics/__init__.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
##########################################################################
|
||||||
|
#
|
||||||
|
# pgAdmin 4 - PostgreSQL Tools
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 - 2016, The pgAdmin Development Team
|
||||||
|
# This software is released under the PostgreSQL Licence
|
||||||
|
#
|
||||||
|
##########################################################################
|
||||||
|
|
||||||
|
"""A blueprint module providing utility functions for the application."""
|
||||||
|
|
||||||
|
from flask import url_for
|
||||||
|
from pgadmin.utils import PgAdminModule
|
||||||
|
|
||||||
|
MODULE_NAME = 'statistics'
|
||||||
|
|
||||||
|
|
||||||
|
class StatisticsModule(PgAdminModule):
|
||||||
|
"""
|
||||||
|
StatisticsModule
|
||||||
|
|
||||||
|
This module will render the statistics of the browser nodes on selection
|
||||||
|
when statistics panel is active.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_own_javascripts(self):
|
||||||
|
return [{
|
||||||
|
'name': 'pgadmin.browser.object_statistics',
|
||||||
|
'path': url_for('statistics.static', filename='js/statistics'),
|
||||||
|
'when': None
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
# Initialise the module
|
||||||
|
blueprint = StatisticsModule(
|
||||||
|
MODULE_NAME, __name__, url_prefix='/misc/statistics'
|
||||||
|
)
|
||||||
301
web/pgadmin/misc/statistics/static/js/statistics.js
Normal file
301
web/pgadmin/misc/statistics/static/js/statistics.js
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
define(
|
||||||
|
['underscore', 'jquery', 'pgadmin.browser', 'backgrid', 'wcdocker', 'pgadmin.backgrid'],
|
||||||
|
function(_, $, pgBrowser, Backgrid) {
|
||||||
|
|
||||||
|
if (pgBrowser.NodeStatistics)
|
||||||
|
return pgBrowser.NodeStatistics;
|
||||||
|
|
||||||
|
pgBrowser.NodeStatistics = pgBrowser.NodeStatistics || {};
|
||||||
|
|
||||||
|
if (pgBrowser.NodeStatistics.initialized) {
|
||||||
|
return pgBrowser.NodeStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
var PGBooleanCell = Backgrid.Extension.SwitchCell.extend({
|
||||||
|
defaults: _.extend({}, Backgrid.Extension.SwitchCell.prototype.defaults)
|
||||||
|
}),
|
||||||
|
typeCellMapper = {
|
||||||
|
// boolean
|
||||||
|
16: PGBooleanCell,
|
||||||
|
// int8
|
||||||
|
20: Backgrid.IntegerCell,
|
||||||
|
// int2
|
||||||
|
21: Backgrid.IntegerCell,
|
||||||
|
// int4
|
||||||
|
23: Backgrid.IntegerCell,
|
||||||
|
// float4
|
||||||
|
700: Backgrid.NumberCell,
|
||||||
|
// float8
|
||||||
|
701: Backgrid.NumberCell,
|
||||||
|
// numeric
|
||||||
|
1700: Backgrid.NumberCell,
|
||||||
|
// abstime
|
||||||
|
702: Backgrid.DatetimeCell,
|
||||||
|
// reltime
|
||||||
|
703: Backgrid.DatetimeCell,
|
||||||
|
// date
|
||||||
|
1082: Backgrid.DatetimeCell.extend({
|
||||||
|
includeDate: true, includeTime: false, includeMilli: false
|
||||||
|
}),
|
||||||
|
// time
|
||||||
|
1083: Backgrid.DatetimeCell.extend({
|
||||||
|
includeDate: false, includeTime: true, includeMilli: true
|
||||||
|
}),
|
||||||
|
// timestamp
|
||||||
|
1114: Backgrid.DatetimeCell.extend({
|
||||||
|
includeDate: true, includeTime: true, includeMilli: true
|
||||||
|
}),
|
||||||
|
// timestamptz
|
||||||
|
1184: 'string'/* Backgrid.DatetimeCell.extend({
|
||||||
|
includeDate: true, includeTime: true, includeMilli: true
|
||||||
|
}) */,
|
||||||
|
1266: 'string'/* Backgrid.DatetimeCell.extend({
|
||||||
|
includeDate: false, includeTime: true, includeMilli: true
|
||||||
|
}) */
|
||||||
|
},
|
||||||
|
GRID_CLASSES = "backgrid presentation table backgrid-striped table-bordered table-hover",
|
||||||
|
wcDocker = window.wcDocker;
|
||||||
|
|
||||||
|
_.extend(
|
||||||
|
PGBooleanCell.prototype.defaults.options, {
|
||||||
|
onText: pgBrowser.messages.TRUE,
|
||||||
|
offText: pgBrowser.messages.FALSE,
|
||||||
|
onColor: 'success',
|
||||||
|
offColor: 'primary',
|
||||||
|
size: 'mini'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
_.extend(pgBrowser.NodeStatistics, {
|
||||||
|
init: function() {
|
||||||
|
if (this.initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.initialized = true;
|
||||||
|
_.bindAll(
|
||||||
|
this,
|
||||||
|
'showStatistics', 'panelVisibilityChanged',
|
||||||
|
'__createMultiLineStatistics', '__createSingleLineStatistics');
|
||||||
|
|
||||||
|
_.extend(
|
||||||
|
this, {
|
||||||
|
initialized: true,
|
||||||
|
collection: new (Backbone.Collection)(null),
|
||||||
|
statistic_columns: [{
|
||||||
|
editable: false,
|
||||||
|
name: 'statistics',
|
||||||
|
label: pgBrowser.messages.STATISTICS_LABEL,
|
||||||
|
cell: 'string',
|
||||||
|
headerCell: Backgrid.Extension.CustomHeaderCell,
|
||||||
|
cellHeaderClasses: 'width_percent_25'
|
||||||
|
},{
|
||||||
|
editable: false,
|
||||||
|
name: 'value',
|
||||||
|
label: pgBrowser.messages.STATISTICS_VALUE_LABEL,
|
||||||
|
cell: 'string'
|
||||||
|
}],
|
||||||
|
panel: pgBrowser.docker.findPanels('statistics'),
|
||||||
|
columns: null,
|
||||||
|
grid: null
|
||||||
|
});
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// We will listen to the visibility change of the statistics panel
|
||||||
|
pgBrowser.Events.on(
|
||||||
|
'pgadmin-browser:panel-statistics:' +
|
||||||
|
wcDocker.EVENT.VISIBILITY_CHANGED,
|
||||||
|
this.panelVisibilityChanged);
|
||||||
|
|
||||||
|
// Hmm.. Did we find the statistics panel, and is it visible (openned)?
|
||||||
|
// If that is the case - we need to listen the browser tree selection
|
||||||
|
// events.
|
||||||
|
if (this.panel.length == 0) {
|
||||||
|
pgBrowser.Events.on(
|
||||||
|
'pgadmin-browser:panel-statistics:' + wcDocker.EVENT.INIT,
|
||||||
|
function() {
|
||||||
|
self.panel = pgBrowser.docker.findPanels('statistics');
|
||||||
|
if (self.panel[0].isVisible() ||
|
||||||
|
self.panel.length != 1) {
|
||||||
|
pgBrowser.Events.on(
|
||||||
|
'pgadmin-browser:tree:selected', this.showStatistics
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}.bind(this)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (self.panel[0].isVisible() ||
|
||||||
|
self.panel.length != 1) {
|
||||||
|
pgBrowser.Events.on(
|
||||||
|
'pgadmin-browser:tree:selected', this.showStatistics
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self.panel.length > 0 && self.panel[0].isVisible()) {
|
||||||
|
pgBrowser.Events.on(
|
||||||
|
'pgadmin-browser:tree:selected', this.showStatistics
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Fetch the actual data and update the collection
|
||||||
|
__updateCollection: function(url, node) {
|
||||||
|
var $container = this.panel[0].layout().scene().find('.pg-panel-content'),
|
||||||
|
$msgContainer = $container.find('.pg-panel-statistics-message'),
|
||||||
|
$gridContainer = $container.find('.pg-panel-statistics-container'),
|
||||||
|
collection = this.collection,
|
||||||
|
panel = this.panel,
|
||||||
|
self = this,
|
||||||
|
msg = '';
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
msg = pgBrowser.messages.NODE_HAS_NO_STATISTICS;
|
||||||
|
/* We fetch the statistics only for those node who set the parameter
|
||||||
|
* showStatistics function.
|
||||||
|
*/
|
||||||
|
if (node.hasStatistics) {
|
||||||
|
/* Set the message because ajax request may take time to
|
||||||
|
* fetch the information from the server.
|
||||||
|
*/
|
||||||
|
msg = '';
|
||||||
|
$msgContainer.text(msg);
|
||||||
|
|
||||||
|
// Set the url, fetch the data and update the collection
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type:'GET',
|
||||||
|
success: function(res) {
|
||||||
|
if (res.data) {
|
||||||
|
var data = res.data;
|
||||||
|
if (node.hasCollectiveStatistics || data['rows'].length > 1) {
|
||||||
|
self.__createMultiLineStatistics.call(self, data);
|
||||||
|
} else {
|
||||||
|
self.__createSingleLineStatistics.call(self, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.grid) {
|
||||||
|
delete self.grid;
|
||||||
|
self.grid = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.grid = new Backgrid.Grid({
|
||||||
|
columns: self.columns,
|
||||||
|
collection: self.collection,
|
||||||
|
className: GRID_CLASSES
|
||||||
|
});
|
||||||
|
self.grid.render();
|
||||||
|
$gridContainer.empty();
|
||||||
|
$gridContainer.append(self.grid.$el);
|
||||||
|
|
||||||
|
if (!$msgContainer.hasClass('hidden')) {
|
||||||
|
$msgContainer.addClass('hidden')
|
||||||
|
}
|
||||||
|
$gridContainer.removeClass('hidden');
|
||||||
|
|
||||||
|
} else if (res.info) {
|
||||||
|
if (!$gridContainer.hasClass('hidden')) {
|
||||||
|
$gridContainer.addClass('hidden')
|
||||||
|
}
|
||||||
|
$msgContainer.text(res.info);
|
||||||
|
$msgContainer.removeClass('hidden');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function() {
|
||||||
|
// TODO:: Report this error.
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (msg != '') {
|
||||||
|
// Hide the grid container and show the default message container
|
||||||
|
if (!$gridContainer.hasClass('hidden'))
|
||||||
|
$gridContainer.addClass('hidden');
|
||||||
|
$msgContainer.removeClass('hidden');
|
||||||
|
|
||||||
|
$msgContainer.text(msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showStatistics: function(item, data, node) {
|
||||||
|
var self = this;
|
||||||
|
/**
|
||||||
|
* We can't start fetching the statistics immediately, it is possible -
|
||||||
|
* the user is just using keyboards to select the node, and just
|
||||||
|
* traversing through.
|
||||||
|
*
|
||||||
|
* We will wait for some time before fetching the statistics for the
|
||||||
|
* selected node.
|
||||||
|
**/
|
||||||
|
if (self.timeout) {
|
||||||
|
clearTimeout(self.timeout);
|
||||||
|
}
|
||||||
|
self.timeout = setTimeout(
|
||||||
|
function() {
|
||||||
|
self.__updateCollection.call(
|
||||||
|
self, node.generate_url(item, 'stats', data, true), node
|
||||||
|
);
|
||||||
|
}, 400);
|
||||||
|
},
|
||||||
|
|
||||||
|
__createMultiLineStatistics: function(data) {
|
||||||
|
var rows = data['rows'],
|
||||||
|
columns = data['columns'];
|
||||||
|
|
||||||
|
this.columns = [];
|
||||||
|
for (var idx in columns) {
|
||||||
|
var c = columns[idx];
|
||||||
|
this.columns.push({
|
||||||
|
editable: false,
|
||||||
|
name: c['name'],
|
||||||
|
cell: typeCellMapper[c['type_code']] || 'string'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collection.reset(rows);
|
||||||
|
},
|
||||||
|
|
||||||
|
__createSingleLineStatistics: function(data) {
|
||||||
|
var row = data['rows'][0],
|
||||||
|
columns = data['columns']
|
||||||
|
res = [];
|
||||||
|
|
||||||
|
this.columns = this.statistic_columns;
|
||||||
|
for (var idx in columns) {
|
||||||
|
name = (columns[idx])['name'];
|
||||||
|
res.push({
|
||||||
|
'statistics': name,
|
||||||
|
'value': row[name]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collection.reset(res);
|
||||||
|
},
|
||||||
|
|
||||||
|
panelVisibilityChanged: function(panel) {
|
||||||
|
if (panel.isVisible()) {
|
||||||
|
var t = pgBrowser.tree,
|
||||||
|
i = t.selected(),
|
||||||
|
d = i && t.itemData(i),
|
||||||
|
n = i && d && pgBrowser.Nodes[d._type];
|
||||||
|
|
||||||
|
pgBrowser.NodeStatistics.showStatistics.apply(
|
||||||
|
pgBrowser.NodeStatistics, [i, d, n]
|
||||||
|
);
|
||||||
|
|
||||||
|
// We will start listening the tree selection event.
|
||||||
|
pgBrowser.Events.on(
|
||||||
|
'pgadmin-browser:tree:selected',
|
||||||
|
pgBrowser.NodeStatistics.showStatistics
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// We don't need to listen the tree item selection event.
|
||||||
|
pgBrowser.Events.off(
|
||||||
|
'pgadmin-browser:tree:selected',
|
||||||
|
pgBrowser.NodeStatistics.showStatistics
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pgBrowser.NodeStatistics;
|
||||||
|
});
|
||||||
@@ -483,7 +483,15 @@ fieldset[disabled] .form-control {
|
|||||||
width:25px;
|
width:25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.backgrid td.renderable:not(.editable):not(.delete-cell) {
|
.backgrid.presentation {
|
||||||
|
background-color: #EEEEEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backgrid.presentation td.renderable {
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backgrid:not(.presentation) td.renderable:not(.editable):not(.delete-cell) {
|
||||||
background-color: #F1F1F1;
|
background-color: #F1F1F1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user