///////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2022, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// import Notify from '../../../static/js/helpers/Notifier'; define('pgadmin.dashboard', [ 'sources/url_for', 'sources/gettext', 'require', 'jquery', 'underscore', 'sources/pgadmin', 'backbone', 'backgrid', 'pgadmin.alertifyjs', 'pgadmin.backform', 'sources/nodes/dashboard', 'sources/window', './ChartsDOM', 'pgadmin.browser', 'bootstrap', 'wcdocker', ], function( url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Alertify, Backform, NodesDashboard, pgWindow, ChartsDOM ) { pgAdmin.Browser = pgAdmin.Browser || {}; var 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, can_signal_backend = 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.$el.attr('tabindex', 0); this.$el.on('keydown', function(e) { // terminating session or cancel the active query. if (e.keyCode == 32) { self.$el.click(); } }); 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 let refresh_grid = () => { $('#btn_refresh').trigger('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'); } Notify.confirm( title, txtConfirm, function() { $.ajax({ url: action_url, type: 'DELETE', }) .done(function(res) { if (res == gettext('Success')) { Notify.success(txtSuccess); refresh_grid(); } else { Notify.error(txtError); } }) .fail(function(xhr, status, error) { Notify.pgRespErrorNotify(xhr, error); }); }, function() { 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.trigger('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, readonly: true, group: gettext('Details'), visible: function() { return this.version >= 100000; }, }, { id: 'query_start', label: gettext('Query started at'), type: 'text', editable: false, readonly: true, group: gettext('Details'), }, { id: 'state_change', label: gettext('Last state changed at'), type: 'text', editable: true, readonly: true, group: gettext('Details'), }, { id: 'query', label: gettext('SQL'), type: 'text', editable: true, readonly: true, control: Backform.SqlFieldControl, group: gettext('Details'), }], }); pgAdmin.Dashboard = { init: function() { if (this.initialized) return; this.initialized = true; this.chartsDomObj = null; this.sid = this.did = -1; this.version = -1; // 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) { var ajaxHook = function() { $.ajax({ url: url, type: 'GET', dataType: 'html', }) .done(function(data) { $(div).html(data); }) .fail(function(xhr, error) { self.onFail(xhr, error, div, ajaxHook); }); }; $(div).html( '
' ); ajaxHook(); // Cache the current IDs for next time $(dashboardPanel).data('sid', -1); $(dashboardPanel).data('did', -1); } } }, onFail: function(xhr, error, div, ajaxHook) { Notify.pgNotifier( error, xhr, gettext('An error occurred whilst loading the dashboard.'), function(msg) { if(msg === 'CRYPTKEY_SET') { ajaxHook(); } else { $(div).html( ' ' ); } } ); }, // Handle Server Disconnect object_disconnected: function() { let item = pgBrowser.tree.selected(), itemData = item && pgBrowser.tree.itemData(item); // The server connected may not be the same one, which was selected, and // we do care out the current selected one only. if (item.length != 0) { this.object_selected(item, itemData); } }, // Handle treeview clicks object_selected: function(item, itemData) { let self = this; if (itemData && itemData._type) { var treeHierarchy = pgBrowser.tree.getTreeNodeHierarchy(item), url = NodesDashboard.url(itemData, item, treeHierarchy); if (url === null) { url = url_for('dashboard.index'); self.version = (treeHierarchy.server && treeHierarchy.server.version) || 0; cancel_query_url = url + 'cancel_query/'; terminate_session_url = url + 'terminate_session/'; // Check if user is super user var server = treeHierarchy['server']; maintenance_database = (server && server.db) || null; can_signal_backend = (server && server.user) ? server.user.can_signal_backend : false; 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 ('database' in treeHierarchy) { self.sid = treeHierarchy.server._id; self.did = treeHierarchy.database._id; is_server_dashboard = false; is_database_dashboard = true; url += self.sid + '/' + self.did; cancel_query_url += self.sid + '/' + self.did + '/'; terminate_session_url += self.sid + '/' + self.did + '/'; } else if ('server' in treeHierarchy) { self.sid = treeHierarchy.server._id; self.did = -1; is_server_dashboard = true; is_database_dashboard = false; url += self.sid; cancel_query_url += self.sid + '/'; terminate_session_url += self.sid + '/'; } else { is_server_dashboard = is_database_dashboard = false; } } else { is_server_dashboard = is_database_dashboard = false; } 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 ) ) { this.chartsDomObj && this.chartsDomObj.unmount(); $(div).empty(); let ajaxHook = function() { $.ajax({ url: url, type: 'GET', dataType: 'html', }) .done(function(data) { $(div).html(data); self.init_dashboard(); }) .fail(function(xhr, error) { self.onFail(xhr, error, div, ajaxHook); }); }; $(div).html( ' ' ); ajaxHook(); $(dashboardPanel).data('server_status', true); } } else { this.chartsDomObj && this.chartsDomObj.unmount(); $(div).html( ' ' ); $(dashboardPanel).data('server_status', false); } // Cache the current IDs for next time $(dashboardPanel).data('dashboard_url', url); } } } }, // Handler function to support the "Add Server" link add_new_server: function() { if (pgBrowser && pgBrowser.tree) { var i = _.isUndefined(pgBrowser.tree.selected()) ? pgBrowser.tree.first(null, false): pgBrowser.tree.selected(), serverModule = require('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, url, columns) { var Datum = Backbone.Model.extend({}), self = this; var path = url + self.sid; if (self.did != -1) { path += '/' + self.did; } var Data = Backbone.Collection.extend({ model: Datum, url: path, mode: 'client', }); var data = new Data(); var HighlightedRow = Backgrid.Row.extend({ render: function() { Backgrid.Row.prototype.render.call(this); var row_type = this.model.get('row_type'); if (_.isUndefined(row_type) || _.isNull(row_type)) { this.$el.removeClass('alert'); this.$el.removeClass('warning'); } else if (row_type === 'warning') { this.$el.addClass('warning'); } else if (row_type === 'alert') { this.$el.addClass('alert'); } return this; } }); // Set up the grid var grid = new Backgrid.Grid({ emptyText: gettext('No data found'), columns: columns, collection: data, className: 'backgrid presentation table table-bordered table-noouter-border table-hover', row: HighlightedRow }); // Render the grid $(container).empty(); $(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, }); filter.setCustomSearchBox($('#txtGridSearch')); // Stash objects for future use $(container).data('data', data); $(container).data('grid', grid); $(container).data('filter', filter); }, __loadMoreRows: function(e) { let elem = e.currentTarget; if ((elem.scrollHeight - 10) < elem.scrollTop + elem.offsetHeight) { if (this.data.length > 0) { this.local_grid.collection.add(this.data.splice(0, 50)); } } }, // Render the data in a grid render_grid_data: function(container) { var that = this; var data = $(container).data('data'), grid = $(container).data('grid'), filter = $(container).data('filter'); if (_.isUndefined(data)) { return null; } data.fetch({ reset: true, success: function(res) { that.data = res.models; that.local_grid = grid; grid.collection.reset(that.data.splice(0,50)); // 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(); } if(that.data.length > 50) { // Listen scroll event to load more rows $('.wcScrollableY').on('scroll', that.__loadMoreRows.bind(that)); } else { // Listen scroll event to load more rows $('.wcScrollableY').off('scroll', that.__loadMoreRows); } // Re-apply search criteria filter.search(); }, error: function(model, xhr) { let err = ''; let msg = ''; let cls = 'info'; if (xhr.readyState === 0) { msg = gettext('Not connected to the server or the connection to the server has been closed.'); } else { err = JSON.parse(xhr.responseText); msg = err.errormsg; // 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.'); } } else { msg = gettext('An error occurred whilst rendering the table.'); cls = 'error'; } } // 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 dashboard init_dashboard: function() { let self = this; this.chartsDomObj = new ChartsDOM.default( document.getElementById('dashboard-graphs'), self.preferences, self.sid, self.did, $('.dashboard-container')[0].clientHeight <=0 ? false : true ); /* Cache may take time to load for the first time * Keep trying till available */ let cacheIntervalId = setInterval(function() { try { if(pgWindow.default.pgAdmin.Browser.preference_version() > 0) { clearInterval(cacheIntervalId); self.reflectPreferences(); } } catch(err) { clearInterval(cacheIntervalId); throw err; } },0); /* Register for preference changed event broadcasted */ pgBrowser.onPreferencesChange('dashboards', function() { self.reflectPreferences(); }); }, reflectPreferences: function() { var self = this; self.preferences = pgWindow.default.pgAdmin.Browser.get_preferences_for_module('dashboards'); this.chartsDomObj.reflectPreferences(self.preferences); if(is_server_dashboard || is_database_dashboard) { if (self.preferences.show_activity && $('#dashboard-activity').hasClass('dashboard-hidden')) { $('#dashboard-activity').removeClass('dashboard-hidden'); } else if(!self.preferences.show_activity) { $('#dashboard-activity').addClass('dashboard-hidden'); } if (self.preferences.show_activity && $('#dashboard-activity').hasClass('dashboard-hidden')) { $('#dashboard-activity').removeClass('dashboard-hidden'); } else if(!self.preferences.show_activity) { $('#dashboard-activity').addClass('dashboard-hidden'); } /* Dashboard specific preferences can be updated in the * appropriate functions */ if(is_server_dashboard) { self.reflectPreferencesServer(); } else if(is_database_dashboard) { self.reflectPreferencesDatabase(); } } }, renderTab: function(e, tab_grid_map) { let prevGrid = tab_grid_map[$(e.relatedTarget).attr('aria-controls')]; $(prevGrid).data('filtertext', $('#txtGridSearch').val()); let currGrid = tab_grid_map[$(e.target).attr('aria-controls')]; $('#txtGridSearch').val($(currGrid).data('filtertext')); pgAdmin.Dashboard.render_grid_data(currGrid); }, reflectPreferencesServer: function() { var self = this; var $dashboardContainer = $('.dashboard-container'); var div_server_activity = $dashboardContainer.find('#server_activity').get(0); var div_server_locks = $dashboardContainer.find('#server_locks').get(0); var div_server_prepared = $dashboardContainer.find('#server_prepared').get(0); var div_server_config = $dashboardContainer.find('#server_config').get(0); var tab_grid_map = { 'tab_server_activity': div_server_activity, 'tab_server_locks': div_server_locks, 'tab_server_prepared': div_server_prepared, 'tab_server_config': div_server_config, }; // Display server activity if (self.preferences.show_activity) { 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 (self.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'] = self.version; }); // Add cancel active query button server_activity_columns.unshift({ name: 'pg-backform-expand', label: '', cell: SessionDetailsCell, cell_priority: -1, postgres_version: self.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: self.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: self.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', }]; // 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, url_for('dashboard.activity'), server_activity_columns ); pgAdmin.Dashboard.render_grid( div_server_locks, url_for('dashboard.locks'), server_locks_columns ); pgAdmin.Dashboard.render_grid( div_server_prepared, url_for('dashboard.prepared'), server_prepared_columns ); pgAdmin.Dashboard.render_grid( div_server_config, 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) { self.renderTab(e, tab_grid_map); }); $('#btn_refresh').off('click').on('click', () => { let currGrid = tab_grid_map[$('#dashboard-activity .nav-tabs .active').attr('aria-controls')]; pgAdmin.Dashboard.render_grid_data(currGrid); }); } }, reflectPreferencesDatabase: function() { var self = this; 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 tab_grid_map = { 'tab_database_activity': div_database_activity, 'tab_database_locks': div_database_locks, 'tab_database_prepared': div_database_prepared, }; // Display server activity if (self.preferences.show_activity) { 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 (self.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'] = self.version; }); // Add cancel active query button database_activity_columns.unshift({ name: 'pg-backform-expand', label: '', cell: SessionDetailsCell, cell_priority: -1, postgres_version: self.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: self.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: self.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', }]; // 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, url_for('dashboard.activity'), database_activity_columns ); pgAdmin.Dashboard.render_grid( div_database_locks, url_for('dashboard.locks'), database_locks_columns ); pgAdmin.Dashboard.render_grid( div_database_prepared, 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) { self.renderTab(e, tab_grid_map); }); $('#btn_refresh').off('click').on('click', () => { let currGrid = tab_grid_map[$('#dashboard-activity .nav-tabs .active').attr('aria-controls')]; pgAdmin.Dashboard.render_grid_data(currGrid); }); } }, toggleVisibility: function(visible, closed=false) { dashboardVisible = visible; if(closed) { this.chartsDomObj && this.chartsDomObj.unmount(); } else { var t = pgBrowser.tree, i = t ? t.selected() : 0, d = i && t.itemData(i); this.chartsDomObj && this.chartsDomObj.setPageVisible(dashboardVisible); this.object_selected(i, d); } }, 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', txtMessage; // 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')) { if (is_cancel_session) { txtMessage = gettext('You cannot cancel background worker processes.'); } else { txtMessage = gettext('You cannot terminate background worker processes.'); } Notify.info(txtMessage); 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) { if (is_cancel_session) { txtMessage = gettext('You are not allowed to cancel the main active session.'); } else { txtMessage = gettext('You are not allowed to terminate the main active session.'); } Notify.error(txtMessage); return false; } else if (is_cancel_session && m.get('state') == 'idle') { // If this session is already idle then do nothing Notify.info( gettext('The session is already in idle state.') ); return false; } else if (can_signal_backend) { // user with membership of 'pg_signal_backend' can terminate the session of non admin user. return true; } 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 if (is_cancel_session) { txtMessage = gettext('Superuser privileges are required to cancel another users query.'); } else { txtMessage = gettext('Superuser privileges are required to terminate another users query.'); } Notify.error(txtMessage); return false; } }, }; return pgAdmin.Dashboard; });