define('pgadmin.dashboard', [ 'sources/url_for', 'sources/gettext', 'require', 'jquery', 'underscore', 'sources/pgadmin', 'backbone', 'backgrid', 'flotr2', 'pgadmin.alertifyjs', 'backgrid.filter', 'pgadmin.browser', 'bootstrap', 'wcdocker' ], function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr, alertify) { var wcDocker = window.wcDocker, pgBrowser = pgAdmin.Browser; /* Return back, this has been called more than once */ if (pgAdmin.Dashboard) return; var dashboardVisible = true, cancel_query_url = '', terminate_session_url = '', is_super_user = false, current_user, maintenance_database, is_server_dashboard = false, is_database_dashboard = false; // Custom BackGrid cell, Responsible for cancelling active sessions var customDashboardActionCell = Backgrid.Extension.DeleteCell.extend({ render: function () { this.$el.empty(); var self = this, cell_action = self.column.get('cell_action'); // if cancel query button then if (cell_action === 'cancel') { this.$el.html( "" ); } else { this.$el.html( "" ); } this.delegateEvents(); return this; }, deleteRow: function(e) { var self = this, title, txtConfirm, txtSuccess, txtError, action_url, cell_action = self.column.get('cell_action'); e.preventDefault(); var canDeleteRow = Backgrid.callByNeed( self.column.get('canDeleteRow'), self.column, self.model ); // If we are not allowed to cancel the query, return from here if(!canDeleteRow) return; // This will refresh the grid var refresh_grid = function() { if(is_server_dashboard) { $('#btn_server_activity_refresh').click(); } else if(is_database_dashboard) { $('#btn_database_activity_refresh').click(); } }; if (cell_action === 'cancel') { title = gettext('Cancel Active Query?'); txtConfirm = gettext('Are you sure you wish to cancel the active query?'); txtSuccess = gettext('Active query cancelled successfully.') txtError = gettext('An error occurred whilst cancelling the active query.') action_url = cancel_query_url + self.model.get('pid') } else { title = gettext('Terminate Session?'); txtConfirm = gettext('Are you sure you wish to terminate the session?'); txtSuccess = gettext('Session terminated successfully.') txtError = gettext('An error occurred whilst terminating the active query.') action_url = terminate_session_url + self.model.get('pid') } alertify.confirm( title, txtConfirm, function(evt) { $.ajax({ url: action_url, type:'DELETE', success: function(res) { if (res == gettext('Success')) { alertify.success(txtSuccess); refresh_grid(); } else { alertify.error(txtError); } }, error: function(xhr, status, error) { try { var err = $.parseJSON(xhr.responseText); if (err.success == 0) { alertify.error(err.errormsg); } } catch (e) {} } }); }, function(evt) { return true; } ); } }); // Subnode Cell, which will display subnode control var SessionDetailsCell = Backgrid.Extension.ObjectCell.extend({ enterEditMode: function () { // Notify that we are about to enter in edit mode for current cell. this.model.trigger("enteringEditMode", [this]); Backgrid.Cell.prototype.enterEditMode.apply(this, arguments); /* Make sure - we listen to the click event */ this.delegateEvents(); var editable = Backgrid.callByNeed(this.column.editable(), this.column, this.model); if (editable) { this.$el.html( "" ); this.model.trigger( "pg-sub-node:opened", this.model, this ); } }, render: function(){ this.$el.empty(); this.$el.html( "" ); this.delegateEvents(); if (this.grabFocus) this.$el.focus(); return this; } }); // Subnode Model var ActiveQueryDetailsModel = Backbone.Model.extend({ defaults: { version: null /* Postgres version */ }, schema: [{ id: 'backend_type', label: gettext('Backend type'), type: 'text', editable: true, disabled: true, group: gettext('Details'), visible: function(m) { return this.version >= 100000; } },{ id: 'query_start', label: gettext('Query started at'), type: 'text', editable: false, disabled: true, group: gettext('Details') },{ id: 'state_change', label: gettext('Last state changed at'), type: 'text', editable: true, disabled: true, group: gettext('Details') },{ id: 'query', label: gettext('SQL'), type: 'text', editable: true, disabled: true, control: Backform.SqlFieldControl, group: gettext('Details') }] }); pgAdmin.Dashboard = { init: function() { if (this.initialized) return; this.initialized = true; // Bind the Dashboard object with the 'object_selected' function var selected = this.object_selected.bind(this); var disconnected = this.object_disconnected.bind(this); // Listen for selection of any of object pgBrowser.Events.on('pgadmin-browser:tree:selected', selected); // Listen for server disconnected event pgBrowser.Events.on('pgadmin:server:disconnect', disconnected); // Load the default welcome dashboard var url = url_for('dashboard.index'); var dashboardPanel = pgBrowser.panels['dashboard'].panel; if (dashboardPanel) { var div = dashboardPanel.layout().scene().find('.pg-panel-content'); if (div) { $.ajax({ url: url, type: "GET", dataType: "html", success: function (data) { $(div).html(data); }, error: function (xhr, status) { $(div).html( '' ); } }); // Cache the current IDs for next time $(dashboardPanel).data('sid', -1) $(dashboardPanel).data('did', -1) } } }, // Handle Server Disconnect object_disconnected: function(obj) { this.object_selected(obj.item, obj.data, pgBrowser.Nodes[obj.data._type]); }, // Handle treeview clicks object_selected: function(item, itemData, node) { if (itemData && itemData._type && dashboardVisible) { var treeHierarchy = node.getTreeNodeHierarchy(item), url = url_for('dashboard.index'), sid = -1, did = -1, b = pgAdmin.Browser, m = b && b.Nodes[itemData._type]; cancel_query_url = url_for('dashboard.index') + 'cancel_query/'; terminate_session_url = url_for('dashboard.index') + 'terminate_session/'; // Check if user is super user var server = treeHierarchy['server']; maintenance_database = (server && server.db) || null; if(server && server.user && server.user.is_superuser) { is_super_user = true; } else { is_super_user = false; // Set current user current_user = (server && server.user) ? server.user.name : null; } if (m && m.dashboard) { if (_.isFunction(m.dashboard)) { url = m.dashboard.apply( item, itemData, node, treeHierarchy ); } else { url = m.dashboard; } } else { if ('database' in treeHierarchy) { sid = treeHierarchy.server._id; did = treeHierarchy.database._id; is_server_dashboard = false; is_database_dashboard = true; url += sid + '/' + did; cancel_query_url += sid + '/' + did + '/'; terminate_session_url += sid + '/' + did + '/'; } else if ('server' in treeHierarchy) { sid = treeHierarchy.server._id; is_server_dashboard = true; is_database_dashboard = false; url += sid; cancel_query_url += sid + '/'; terminate_session_url += sid + '/'; } } var dashboardPanel = pgBrowser.panels['dashboard'].panel; if (dashboardPanel) { var div = dashboardPanel.layout().scene().find( '.pg-panel-content' ); if (div) { if (itemData.connected || _.isUndefined(itemData.connected)) { // Avoid unnecessary reloads if (url != $(dashboardPanel).data('dashboard_url') || (url == $(dashboardPanel).data('dashboard_url') && $(dashboardPanel).data('server_status') == false )) { // Clear out everything so any existing timers die off $(div).empty(); $.ajax({ url: url, type: "GET", dataType: "html", success: function (data) { $(div).html(data); }, error: function (xhr, status) { $(div).html( '' ); } }); $(dashboardPanel).data('server_status', true); } } else { $(div).empty(); $(div).html( '' ); $(dashboardPanel).data('server_status', false); } // Cache the current IDs for next time $(dashboardPanel).data('dashboard_url', url); } } } }, // Render a chart render_chart: function(container, data, dataset, sid, did, url, options, counter, refresh) { // Data format: // [ // { data: [[0, y0], [1, y1]...], label: 'Label 1', [options] }, // { data: [[0, y0], [1, y1]...], label: 'Label 2', [options] }, // { data: [[0, y0], [1, y1]...], label: 'Label 3', [options] } // ] if (!dashboardVisible) return; var y = 0; if (dataset.length == 0) { if (counter == true) { // Have we stashed initial values? if (_.isUndefined($(container).data('counter_previous_vals'))) { $(container).data('counter_previous_vals', data[0]) } else { // Create the initial data structure for (var x in data[0]) { dataset.push({ 'data': [[0, data[0][x] - $(container).data('counter_previous_vals')[x]]], 'label': x }); } } } else { // Create the initial data structure for (var x in data[0]) { dataset.push({ 'data': [[0, data[0][x]]], 'label': x }); } } } else { for (var x in data[0]) { // Push new values onto the existing data structure // If this is a counter stat, we need to subtract the previous value if (counter == false) { dataset[y]['data'].unshift([0, data[0][x]]); } else { // Store the current value, minus the previous one we stashed. // It's possible the tab has been reloaded, in which case out previous values are gone if (_.isUndefined($(container).data('counter_previous_vals'))) return dataset[y]['data'].unshift([0, data[0][x] - $(container).data('counter_previous_vals')[x]]); } // Reset the time index to get a proper scrolling display for (var z = 0; z < dataset[y]['data'].length; z++) { dataset[y]['data'][z][0] = z; } y++; } $(container).data('counter_previous_vals', data[0]) } // Remove uneeded elements for (x = 0; x < dataset.length; x++) { // Remove old data points if (dataset[x]['data'].length > 101) { dataset[x]['data'].pop(); } } // Draw Graph, if the container still exists and has a size var dashboardPanel = pgBrowser.panels['dashboard'].panel; var div = dashboardPanel.layout().scene().find('.pg-panel-content'); if ($(div).find(container).length) { // Exists? if (container.clientHeight > 0 && container.clientWidth > 0) { // Not hidden? Flotr.draw(container, dataset, options); } } else { return; } // Animate var setTimeoutFunc = function () { var path = url + sid; if (did != -1) { path += '/' + did; } $.ajax({ url: path, type: "GET", dataType: "html", success: function (resp) { $(container).removeClass('graph-error') data = JSON.parse(resp); pgAdmin.Dashboard.render_chart(container, data, dataset, sid, did, url, options, counter, refresh); }, error: function (xhr, status, msg) { var err = $.parseJSON(xhr.responseText), msg = err.errormsg, cls; // If we get a 428, it means the server isn't connected if (xhr.status == 428) { if (_.isUndefined(msg) || _.isNull(msg)) { msg = gettext('Please connect to the selected server to view the graph.'); } cls = 'info'; } else { msg = gettext('An error occurred whilst rendering the graph.'); cls = 'danger'; } $(container).addClass('graph-error'); $(container).html( '' ); // Try again... if (container.clientHeight > 0 && container.clientWidth > 0) { setTimeout(setTimeoutFunc, refresh * 1000); } }, }); }; setTimeout(setTimeoutFunc, refresh * 1000); }, // Handler function to support the "Add Server" link add_new_server: function() { if (pgBrowser && pgBrowser.tree) { var i = pgBrowser.tree.selected().length != 0 ? pgBrowser.tree.selected() : pgBrowser.tree.first(null, false), serverModule = r('pgadmin.node.server'), itemData = pgBrowser.tree.itemData(i); while (itemData && itemData._type != "server_group") { i = pgBrowser.tree.next(i); itemData = pgBrowser.tree.itemData(i); } if (!itemData) { return; } if (serverModule) { serverModule.callbacks.show_obj_properties.apply( serverModule, [{action: 'create'}, i] ); } } }, // Render a grid render_grid: function(container, sid, did, url, columns) { var Datum = Backbone.Model.extend({}); var path = url + sid; if (did != -1) { path += '/' + did; } var Data = Backbone.Collection.extend({ model: Datum, url: path, mode: "client" }); var data = new Data(); // Set up the grid var grid = new Backgrid.Grid({ columns: columns, collection: data, className: "backgrid table-bordered presentation table backgrid-striped" }); // Render the grid $(container).append(grid.render().el) // Initialize a client-side filter to filter on the client // mode pageable collection's cache. var filter = new Backgrid.Extension.ClientSideFilter({ collection: data }); // Render the filter $('#' + container.id + '_filter').before(filter.render().el); // Add some space to the filter and move it to the right $(filter.el).css({float: "right", margin: "5px", "margin-right": "2px", "margin-top": "3px"}); // Stash objects for future use $(container).data('data', data); $(container).data('grid', grid); $(container).data('filter', filter); }, // Render the data in a grid render_grid_data: function(container) { var data = $(container).data('data'), grid = $(container).data('grid'), filter = $(container).data('filter'); if(_.isUndefined(data)){ return null; } data.fetch({ reset: true, success: function() { // If we're showing an error, remove it, and replace the grid & filter if ($(container).hasClass('grid-error')) { $(container).removeClass('grid-error'); $(container).html(grid.render().el) $(filter.el).show(); } // Re-apply search criteria filter.search(); }, error: function(model, xhr, options) { var err = $.parseJSON(xhr.responseText), msg = err.errormsg, cls; // If we get a 428, it means the server isn't connected if (xhr.status == 428) { if (_.isUndefined(msg) || _.isNull(msg)) { msg = gettext('Please connect to the selected server to view the table.'); } cls = 'info'; } else { msg = gettext('An error occurred whilst rendering the table.'); cls = 'danger'; } // Replace the content with the error, if not already present. Always update the message if (!$(container).hasClass('grid-error')) { $(filter.el).hide(); $(container).addClass('grid-error'); } $(container).html( '' ); // Try again setTimeout(function() { pgAdmin.Dashboard.render_grid_data(container, data); }, 5000) } }); }, // Rock n' roll on the server dashboard init_server_dashboard: function(sid, version, session_stats_refresh, tps_stats_refresh, ti_stats_refresh, to_stats_refresh, bio_stats_refresh) { var div_sessions = $('.dashboard-container').find('#graph-sessions')[0]; var div_tps = $('.dashboard-container').find('#graph-tps')[0]; var div_ti = $('.dashboard-container').find('#graph-ti')[0]; var div_to = $('.dashboard-container').find('#graph-to')[0]; var div_bio = $('.dashboard-container').find('#graph-bio')[0]; var div_server_activity = $('.dashboard-container').find('#server_activity'); var div_server_locks = $('.dashboard-container').find('#server_locks'); var div_server_prepared = $('.dashboard-container').find('#server_prepared'); var div_server_config = $('.dashboard-container').find('#server_config'); var dataset_sessions = []; var data_sessions = []; var dataset_tps = []; var data_tps = []; var dataset_ti = []; var data_ti = []; var dataset_to = []; var data_to = []; var dataset_bio = []; var data_bio = []; // Fake DB ID var did = -1; var options_line = { parseFloat: false, xaxis: { min: 100, max: 0, autoscale: 0 }, yaxis : { autoscale: 1 }, legend : { position : 'nw', backgroundColor : '#D2E8FF' } } var server_activity_columns = [{ name: "pid", label: gettext('PID'), editable: false, cell: "string" }, { name: "datname", label: gettext('Database'), editable: false, cell: "string" }, { name: "usename", label: gettext('User'), editable: false, cell: "string" }, { name: "application_name", label: gettext('Application'), editable: false, cell: "string" }, { name: "client_addr", label: gettext('Client'), editable: false, cell: "string" }, { name: "backend_start", label: gettext('Backend start'), editable: false, cell: "string" }, { name: "state", label: gettext('State'), editable: false, cell: "string" }]; if (version < 90600) { server_activity_columns = server_activity_columns.concat( [{ name: "waiting", label: gettext('Waiting?'), editable: false, cell: "string" }]); } else { server_activity_columns = server_activity_columns.concat( [{ name: "wait_event", label: gettext('Wait Event'), editable: false, cell: "string" },{ name: "blocking_pids", label: gettext('Blocking PIDs'), editable: false, cell: "string" }]); } var newActiveQueryDetailsModel = new ActiveQueryDetailsModel(); var subNodeFieldsModel = Backform.generateViewSchema( null, newActiveQueryDetailsModel, 'create', null, null, true ); // Add version to each field _.each(subNodeFieldsModel[0].fields, function(obj) { obj['version'] = version; }); // Add cancel active query button server_activity_columns.unshift({ name: "pg-backform-expand", label: "", cell: SessionDetailsCell, cell_priority: -1, postgres_version: version, schema: subNodeFieldsModel }); // Add cancel active query button server_activity_columns.unshift({ name: "pg-backform-delete", label: "", cell: customDashboardActionCell, cell_action: 'cancel', editable: false, cell_priority: -1, canDeleteRow: pgAdmin.Dashboard.can_take_action, postgres_version: version }); server_activity_columns.unshift({ name: "pg-backform-delete", label: "", cell: customDashboardActionCell, cell_action: 'terminate', editable: false, cell_priority: -1, canDeleteRow: pgAdmin.Dashboard.can_take_action, postgres_version: version }); var server_locks_columns = [{ name: "pid", label: gettext('PID'), editable: false, cell: "string" }, { name: "datname", label: gettext('Database'), editable: false, cell: "string" }, { name: "locktype", label: gettext('Lock type'), editable: false, cell: "string" }, { name: "relation", label: gettext('Target relation'), editable: false, cell: "string" }, { name: "page", label: gettext('Page'), editable: false, cell: "string" }, { name: "tuple", label: gettext('Tuple'), editable: false, cell: "string" }, { name: "virtualxid", label: gettext('vXID (target)'), editable: false, cell: "string" }, { name: "transactionid", label: gettext('XID (target)'), editable: false, cell: "string" },{ name: "classid", label: gettext('Class'), editable: false, cell: "string" },{ name: "objid", label: gettext('Object ID'), editable: false, cell: "string" },{ name: "virtualtransaction", label: gettext('vXID (owner)'), editable: false, cell: "string" },{ name: "mode", label: gettext('Mode'), editable: false, cell: "string" },{ name: "granted", label: gettext('Granted?'), editable: false, cell: "string" }]; var server_prepared_columns = [{ name: "git", label: gettext('Name'), editable: false, cell: "string" }, { name: "database", label: gettext('Database'), editable: false, cell: "string" }, { name: "Owner", label: gettext('Owner'), editable: false, cell: "string" }, { name: "transaction", label: gettext('XID'), editable: false, cell: "string" }, { name: "prepared", label: gettext('Prepared at'), editable: false, cell: "string" }]; var server_config_columns = [{ name: "name", label: gettext('Name'), editable: false, cell: "string" }, { name: "category", label: gettext('Category'), editable: false, cell: "string" }, { name: "setting", label: gettext('Setting'), editable: false, cell: "string" }, { name: "unit", label: gettext('Unit'), editable: false, cell: "string" }, { name: "short_desc", label: gettext('Description'), editable: false, cell: "string" }]; // Render the graphs pgAdmin.Dashboard.render_chart( div_sessions, data_sessions, dataset_sessions, sid, did, url_for('dashboard.session_stats'), options_line, false, session_stats_refresh ); pgAdmin.Dashboard.render_chart( div_tps, data_tps, dataset_tps, sid, did, url_for('dashboard.tps_stats'), options_line, true, tps_stats_refresh ); pgAdmin.Dashboard.render_chart( div_ti, data_ti, dataset_ti, sid, did, url_for('dashboard.ti_stats'), options_line, true, ti_stats_refresh ); pgAdmin.Dashboard.render_chart( div_to, data_to, dataset_to, sid, did, url_for('dashboard.to_stats'), options_line, true, to_stats_refresh ); pgAdmin.Dashboard.render_chart( div_bio, data_bio, dataset_bio, sid, did, url_for('dashboard.bio_stats'), options_line, true, bio_stats_refresh ); // To align subnode controls properly $(div_server_activity).addClass('pg-el-container'); $(div_server_activity).attr('el', 'sm'); // Render the tabs, but only get data for the activity tab for now pgAdmin.Dashboard.render_grid( div_server_activity, sid, did, url_for('dashboard.activity'), server_activity_columns ); pgAdmin.Dashboard.render_grid( div_server_locks, sid, did, url_for('dashboard.locks'), server_locks_columns ); pgAdmin.Dashboard.render_grid( div_server_prepared, sid, did, url_for('dashboard.prepared'), server_prepared_columns ); pgAdmin.Dashboard.render_grid( div_server_config, sid, did, url_for('dashboard.config'), server_config_columns ); pgAdmin.Dashboard.render_grid_data(div_server_activity); // (Re)render the appropriate tab $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { switch ($(e.target).attr('aria-controls')) { case "tab_server_activity": pgAdmin.Dashboard.render_grid_data(div_server_activity); break; case "tab_server_locks": pgAdmin.Dashboard.render_grid_data(div_server_locks); break; case "tab_server_prepared": pgAdmin.Dashboard.render_grid_data(div_server_prepared); break; case "tab_server_config": pgAdmin.Dashboard.render_grid_data(div_server_config); break; } }); // Handle button clicks $("button").click(function(e){ switch(this.id) { case "btn_server_activity_refresh": pgAdmin.Dashboard.render_grid_data(div_server_activity); break; case "btn_server_locks_refresh": pgAdmin.Dashboard.render_grid_data(div_server_locks); break; case "btn_server_prepared_refresh": pgAdmin.Dashboard.render_grid_data(div_server_prepared); break; case "btn_server_config_refresh": pgAdmin.Dashboard.render_grid_data(div_server_config); break; } }); }, // Rock n' roll on the database dashboard init_database_dashboard: function(sid, did, version, session_stats_refresh, tps_stats_refresh, ti_stats_refresh, to_stats_refresh, bio_stats_refresh) { var div_sessions = document.getElementById('graph-sessions'); var div_tps = document.getElementById('graph-tps'); var div_ti = document.getElementById('graph-ti'); var div_to = document.getElementById('graph-to'); var div_bio = document.getElementById('graph-bio'); var div_database_activity = document.getElementById('database_activity'); var div_database_locks = document.getElementById('database_locks'); var div_database_prepared = document.getElementById('database_prepared'); var dataset_sessions = []; var data_sessions = []; var dataset_tps = []; var data_tps = []; var dataset_ti = []; var data_ti = []; var dataset_to = []; var data_to = []; var dataset_bio = []; var data_bio = []; var options_line = { parseFloat: false, xaxis: { min: 100, max: 0, autoscale: 0 }, yaxis : { autoscale: 1 }, legend : { position : 'nw', backgroundColor : '#D2E8FF' } } var database_activity_columns = [{ name: "pid", label: gettext('PID'), editable: false, cell: "string" }, { name: "usename", label: gettext('User'), editable: false, cell: "string" }, { name: "application_name", label: gettext('Application'), editable: false, cell: "string" }, { name: "client_addr", label: gettext('Client'), editable: false, cell: "string" }, { name: "backend_start", label: gettext('Backend start'), editable: false, cell: "string" }, { name: "state", label: gettext('State'), editable: false, cell: "string" }]; if (version < 90600) { database_activity_columns = database_activity_columns.concat( [{ name: "waiting", label: gettext('Waiting?'), editable: false, cell: "string" }]); } else { database_activity_columns = database_activity_columns.concat( [{ name: "wait_event", label: gettext('Wait Event'), editable: false, cell: "string" },{ name: "blocking_pids", label: gettext('Blocking PIDs'), editable: false, cell: "string" }]); } var newActiveQueryDetailsModel = new ActiveQueryDetailsModel(); var subNodeFieldsModel = Backform.generateViewSchema( null, newActiveQueryDetailsModel, 'create', null, null, true ); // Add version to each field _.each(subNodeFieldsModel[0].fields, function(obj) { obj['version'] = version; }); // Add cancel active query button database_activity_columns.unshift({ name: "pg-backform-expand", label: "", cell: SessionDetailsCell, cell_priority: -1, postgres_version: version, schema: subNodeFieldsModel }); database_activity_columns.unshift({ name: "pg-backform-delete", label: "", cell: customDashboardActionCell, cell_action: 'cancel', editable: false, cell_priority: -1, canDeleteRow: pgAdmin.Dashboard.can_take_action, postgres_version: version }); database_activity_columns.unshift({ name: "pg-backform-delete", label: "", cell: customDashboardActionCell, cell_action: 'terminate', editable: false, cell_priority: -1, canDeleteRow: pgAdmin.Dashboard.can_take_action, postgres_version: version }); var database_locks_columns = [{ name: "pid", label: gettext('PID'), editable: false, cell: "string" }, { name: "locktype", label: gettext('Lock type'), editable: false, cell: "string" }, { name: "relation", label: gettext('Target relation'), editable: false, cell: "string" }, { name: "page", label: gettext('Page'), editable: false, cell: "string" }, { name: "tuple", label: gettext('Tuple'), editable: false, cell: "string" }, { name: "virtualxid", label: gettext('vXID (target)'), editable: false, cell: "string" }, { name: "transactionid", label: gettext('XID (target)'), editable: false, cell: "string" },{ name: "classid", label: gettext('Class'), editable: false, cell: "string" },{ name: "objid", label: gettext('Object ID'), editable: false, cell: "string" },{ name: "virtualtransaction", label: gettext('vXID (owner)'), editable: false, cell: "string" },{ name: "mode", label: gettext('Mode'), editable: false, cell: "string" },{ name: "granted", label: gettext('Granted?'), editable: false, cell: "string" }]; var database_prepared_columns = [{ name: "git", label: gettext('Name'), editable: false, cell: "string" }, { name: "Owner", label: gettext('Owner'), editable: false, cell: "string" }, { name: "transaction", label: gettext('XID'), editable: false, cell: "string" }, { name: "prepared", label: gettext('Prepared at'), editable: false, cell: "string" }]; // Render the graphs pgAdmin.Dashboard.render_chart( div_sessions, data_sessions, dataset_sessions, sid, did, url_for('dashboard.session_stats'), options_line, false, session_stats_refresh ); pgAdmin.Dashboard.render_chart( div_tps, data_tps, dataset_tps, sid, did, url_for('dashboard.tps_stats'), options_line, true, tps_stats_refresh ); pgAdmin.Dashboard.render_chart( div_ti, data_ti, dataset_ti, sid, did, url_for('dashboard.ti_stats'), options_line, true, ti_stats_refresh ); pgAdmin.Dashboard.render_chart( div_to, data_to, dataset_to, sid, did, url_for('dashboard.to_stats'), options_line, true, to_stats_refresh ); pgAdmin.Dashboard.render_chart( div_bio, data_bio, dataset_bio, sid, did, url_for('dashboard.bio_stats'), options_line, true, bio_stats_refresh ); // To align subnode controls properly $(div_database_activity).addClass('pg-el-container'); $(div_database_activity).attr('el', 'sm'); // Render the tabs, but only get data for the activity tab for now pgAdmin.Dashboard.render_grid( div_database_activity, sid, did, url_for('dashboard.activity'), database_activity_columns ); pgAdmin.Dashboard.render_grid( div_database_locks, sid, did, url_for('dashboard.locks'), database_locks_columns ); pgAdmin.Dashboard.render_grid( div_database_prepared, sid, did, url_for('dashboard.prepared'), database_prepared_columns ); pgAdmin.Dashboard.render_grid_data(div_database_activity); // (Re)render the appropriate tab $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { switch ($(e.target).attr('aria-controls')) { case "tab_database_activity": pgAdmin.Dashboard.render_grid_data(div_database_activity); break; case "tab_database_locks": pgAdmin.Dashboard.render_grid_data(div_database_locks); break; case "tab_database_prepared": pgAdmin.Dashboard.render_grid_data(div_database_prepared); break; } }); // Handle button clicks $("button").click(function(e){ switch(this.id) { case "btn_database_activity_refresh": pgAdmin.Dashboard.render_grid_data(div_database_activity); break; case "btn_database_locks_refresh": pgAdmin.Dashboard.render_grid_data(div_database_locks); break; case "btn_database_prepared_refresh": pgAdmin.Dashboard.render_grid_data(div_database_prepared); break; } }); }, toggleVisibility: function(flag) { dashboardVisible = flag; }, can_take_action: function(m) { // We will validate if user is allowed to cancel the active query // If there is only one active session means it probably our main // connection session var active_sessions = m.collection.where({'state': 'active'}), pg_version = this.get('postgres_version') || null, cell_action = this.get('cell_action') || null, is_cancel_session = cell_action === 'cancel', txtAction = is_cancel_session ? gettext('cancel') : gettext('terminate'); // With PG10, We have background process showing on dashboard // We will not allow user to cancel them as they will fail with error // anyway, so better usability we will throw our on notification // Background processes do not have database field populated if (pg_version && pg_version >= 100000 && !m.get('datname')) { alertify.info( gettext('You cannot ') + txtAction + gettext(' background worker processes.') ); return false; // If it is the last active connection on maintenance db then error out } else if (maintenance_database == m.get('datname') && m.get('state') == 'active' && active_sessions.length == 1) { alertify.error( gettext('You are not allowed to ') + txtAction + gettext(' the main active session.') ); return false; } else if(is_cancel_session && m.get('state') == 'idle') { // If this session is already idle then do nothing alertify.info( gettext('The session is already in idle state.') ); return false; } else if(is_super_user) { // Super user can do anything return true; } else if (current_user && current_user == m.get('usename')) { // Non-super user can cancel only their active queries return true; } else { // Do not allow to cancel someone else session to non-super user alertify.error( gettext('Superuser privileges are required to ') + txtAction + gettext(' another users query.') ); return false; } } }; return pgAdmin.Dashboard; });