diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py index a41492f4f..2214f5354 100644 --- a/web/pgadmin/dashboard/__init__.py +++ b/web/pgadmin/dashboard/__init__.py @@ -488,3 +488,29 @@ def cancel_query(sid=None, did=None, pid=None): response=gettext("Success") if res else gettext("Failed"), status=200 ) + +@blueprint.route( + '/terminate_session//', methods=['DELETE'] +) +@blueprint.route( + '/terminate_session///', methods=['DELETE'] +) +@login_required +@check_precondition +def terminate_session(sid=None, did=None, pid=None): + """ + This function terminate the specific session + :param sid: server id + :param did: database id + :param pid: session/process id + :return: Response + """ + sql = "SELECT pg_terminate_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 + ) diff --git a/web/pgadmin/dashboard/static/css/dashboard.css b/web/pgadmin/dashboard/static/css/dashboard.css index c18879545..7332e8870 100644 --- a/web/pgadmin/dashboard/static/css/dashboard.css +++ b/web/pgadmin/dashboard/static/css/dashboard.css @@ -167,4 +167,9 @@ border: 1px solid #cccccc; } +.dashboard-container .delete-cell .fa-times-circle:before { + color: red; + font-size: 14px; +} + /* CSS to make subnode control look preety in backgrid - END */ diff --git a/web/pgadmin/dashboard/static/js/dashboard.js b/web/pgadmin/dashboard/static/js/dashboard.js index 0e709ecb8..b464968e3 100644 --- a/web/pgadmin/dashboard/static/js/dashboard.js +++ b/web/pgadmin/dashboard/static/js/dashboard.js @@ -16,6 +16,7 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr, var dashboardVisible = true, cancel_query_url = '', + terminate_session_url = '', is_super_user = false, current_user, maintenance_database, is_server_dashboard = false, @@ -26,7 +27,7 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr, render: function () { this.$el.empty(); this.$el.html( - "" ); @@ -88,6 +89,72 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr, } }); + var terminateSessionCell = Backgrid.Extension.DeleteCell.extend({ + render: function () { + this.$el.empty(); + this.$el.html( + "" + ); + this.delegateEvents(); + return this; + }, + deleteRow: function(e) { + var self = this; + 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(); + } + }; + + var title = gettext('Terminate Session?'), + txtConfirm = gettext('Are you sure you wish to terminate the session?'); + + alertify.confirm( + title, + txtConfirm, + function(evt) { + $.ajax({ + url: terminate_session_url + self.model.get('pid'), + type:'DELETE', + success: function(res) { + if (res == gettext('Success')) { + alertify.success(gettext('Active query terminateed successfully.')); + refresh_grid(); + } else { + alertify.error(gettext('An error occurred whilst terminateing the active query.')); + } + }, + 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 () { @@ -210,6 +277,7 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr, 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']; @@ -239,12 +307,14 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr, 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 + '/'; } } @@ -673,6 +743,14 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr, postgres_version: version }); + server_activity_columns.unshift({ + name: "pg-backform-delete", label: "", + cell: terminateSessionCell, + editable: false, cell_priority: -1, + canDeleteRow: pgAdmin.Dashboard.can_terminate_session, + postgres_version: version + }); + var server_locks_columns = [{ name: "pid", label: gettext('PID'), @@ -1009,6 +1087,13 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr, canDeleteRow: pgAdmin.Dashboard.can_cancel_active_query, postgres_version: version }); + database_activity_columns.unshift({ + name: "pg-backform-delete", label: "", + cell: terminateSessionCell, + editable: false, cell_priority: -1, + canDeleteRow: pgAdmin.Dashboard.can_terminate_session, + postgres_version: version + }); var database_locks_columns = [{ name: "pid", @@ -1222,7 +1307,45 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr, ); return false; } - } + }, + can_terminate_session: 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; + + // 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 terminate 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 terminate the main active session.') + ); + 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 terminate 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 terminate another users query.') + ); + return false; + } + }, }; return pgAdmin.Dashboard;