hiHi, PFA patch to add functionality which will allow super user to cancel long running queries from dashboard. RM#1812
*Steps used to test:* 1) Open psql session, Connect to 'test' database on respective server 2) Execute "select pg_sleep(1000);" 3) Open pgAdmin4 4) Connect to respective server 5) Click on Dashboard 6) Check "Sessions" tab under "Server activity" section then look for active sessions for test database. 7) Click on cancel button and cancel the active session 8) Check psql session now, you will see "ERROR: canceling statement due to user request" -- Regards, Murtuza Zabuawala EnterpriseDB: http://www.enterprisedb.com The Enterprise PostgreSQL Company
diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py index 8f813d7..897bfc0 100644 --- a/web/pgadmin/dashboard/__init__.py +++ b/web/pgadmin/dashboard/__init__.py @@ -409,3 +409,36 @@ def config(sid=None): :return: """ return get_data(sid, None, 'config.sql') + + +@blueprint.route( + '/cancel_session/<int:sid>/<int:pid>', methods=['DELETE'] +) +@blueprint.route( + '/cancel_session/<int:sid>/<int:did>/<int:pid>', methods=['DELETE'] +) +@login_required +@check_precondition +def cancel_session(sid=None, did=None, pid=None): + """ + This function cancel the specific session + :param sid: server id + :param did: database id + :param pid: session/process id + :return: Response + """ + user = g.manager.user_info + if user and user.get('is_superuser'): + sql = "SELECT pg_cancel_backend({0});".format(pid) + status, res = g.conn.execute_scalar(sql) + if not status: + return internal_server_error(errormsg=res) + + return ajax_response( + response=gettext("Success") if res else gettext("Failed"), + status=200 + ) + else: + return internal_server_error( + errormsg=gettext("User must be a superuser to perform this task.") + ) diff --git a/web/pgadmin/dashboard/templates/dashboard/js/dashboard.js b/web/pgadmin/dashboard/templates/dashboard/js/dashboard.js index 34a1ab7..35f5a2b 100644 --- a/web/pgadmin/dashboard/templates/dashboard/js/dashboard.js +++ b/web/pgadmin/dashboard/templates/dashboard/js/dashboard.js @@ -1,8 +1,9 @@ define([ - 'require', 'jquery', 'pgadmin', 'underscore', 'backbone', 'sources/gettext', 'flotr2', 'wcdocker', + 'require', 'jquery', 'pgadmin', 'underscore', 'backbone', 'sources/gettext', + 'alertify', 'sources/alerts/alertify_wrapper', 'flotr2', 'wcdocker', 'pgadmin.browser', 'bootstrap' ], -function(r, $, pgAdmin, _, Backbone, gettext) { +function(r, $, pgAdmin, _, Backbone, gettext, alertify, AlertifyWrapper) { var wcDocker = window.wcDocker, pgBrowser = pgAdmin.Browser; @@ -11,7 +12,65 @@ function(r, $, pgAdmin, _, Backbone, gettext) { if (pgAdmin.Dashboard) return; - var dashboardVisible = true; + var dashboardVisible = true, + cancel_session_url = '', + is_super_user = false; + + // Custom BackGrid cell, Responsible for cancelling active sessions + var cancelSessionCell = Backgrid.Extension.DeleteCell.extend({ + render: function () { + this.$el.empty(); + this.$el.html( + "<i class='fa fa-times-circle' data-toggle='tooltip' " + + "title='" + gettext('Cancel the current session from backend') + + "'></i>" + ); + this.delegateEvents(); + return this; + }, + deleteRow: function(e) { + var self = this; + e.preventDefault(); + var _title = gettext('Cancel Active Session?'), + _txtConfirm = gettext('Are you sure you wish to cancel this active session?'); + + if (self.model.get('state') == 'idle') { + _title = gettext('Cancel Idle Session?'); + _txtConfirm = gettext('Are you sure you wish to cancel this idle session?'); + } + + alertify.confirm( + _title, + _txtConfirm, + function(evt) { + $.ajax({ + url: cancel_session_url + self.model.get('pid'), + type:'DELETE', + success: function(res) { + var alertifyWrapper = new AlertifyWrapper(); + if (res == gettext('Success')) { + alertifyWrapper.success(gettext('Session cancelled successfully.')); + } else { + alertifyWrapper.error(gettext('Error during canceling session.')); + } + }, + error: function(xhr, status, error) { + try { + var err = $.parseJSON(xhr.responseText); + if (err.success == 0) { + var alertifyWrapper = new AlertifyWrapper(); + alertifyWrapper.error(err.errormsg); + } + } catch (e) {} + } + }); + }, + function(evt) { + return true; + } + ); + } + }); pgAdmin.Dashboard = { init: function() { @@ -63,6 +122,16 @@ function(r, $, pgAdmin, _, Backbone, gettext) { sid = -1, did = -1, b = pgAdmin.Browser, m = b && b.Nodes[itemData._type]; + cancel_session_url = '{{ url_for('dashboard.index') }}' + 'cancel_session/'; + + // Check if user is super user + var server = treeHierarchy['server']; + if(server && server.user && server.user.is_superuser) { + is_super_user = true; + } else { + is_super_user = false; + } + if (m && m.dashboard) { if (_.isFunction(m.dashboard)) { url = m.dashboard.apply( @@ -76,9 +145,11 @@ function(r, $, pgAdmin, _, Backbone, gettext) { sid = treeHierarchy.server._id; did = treeHierarchy.database._id; url += sid + '/' + did; + cancel_session_url += sid + '/' + did + '/'; } else if ('server' in treeHierarchy) { sid = treeHierarchy.server._id; url += sid; + cancel_session_url += sid + '/'; } } @@ -463,6 +534,16 @@ function(r, $, pgAdmin, _, Backbone, gettext) { }]); } + // If user is super user + if(is_super_user) { + server_activity_columns.unshift({ + name: "pg-backform-delete", label: "", + cell: cancelSessionCell, + editable: false, cell_priority: -1, + canDeleteRow: true + }); + } + var server_locks_columns = [{ name: "pid", label: gettext('PID'), @@ -735,6 +816,16 @@ function(r, $, pgAdmin, _, Backbone, gettext) { }]); } + // If user is super user + if(is_super_user) { + database_activity_columns.unshift({ + name: "pg-backform-delete", label: "", + cell: cancelSessionCell, + editable: false, cell_priority: -1, + canDeleteRow: true + }); + } + var database_locks_columns = [{ name: "pid", label: gettext('PID'),