diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py index b91ccb2..a4c18c9 100644 --- a/web/pgadmin/dashboard/__init__.py +++ b/web/pgadmin/dashboard/__init__.py @@ -456,3 +456,30 @@ def config(sid=None): :return: """ return get_data(sid, None, 'config.sql') + + +@blueprint.route( + '/cancel_session//', methods=['DELETE'] +) +@blueprint.route( + '/cancel_session///', 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 + """ + 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 + ) diff --git a/web/pgadmin/dashboard/templates/dashboard/js/dashboard.js b/web/pgadmin/dashboard/templates/dashboard/js/dashboard.js index 8bd62d6..205639d 100644 --- a/web/pgadmin/dashboard/templates/dashboard/js/dashboard.js +++ b/web/pgadmin/dashboard/templates/dashboard/js/dashboard.js @@ -1,9 +1,11 @@ define('pgadmin.dashboard', [ 'sources/url_for', 'sources/gettext', 'require', 'jquery', 'underscore', - 'pgadmin', 'backbone', 'backgrid', 'flotr2', 'backgrid.filter', + 'pgadmin', 'backbone', 'backgrid', 'flotr2', 'alertify', + 'sources/alerts/alertify_wrapper', 'backgrid.filter', 'pgadmin.browser', 'bootstrap', 'wcdocker' ], -function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr) { +function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr, + alertify, AlertifyWrapper) { var wcDocker = window.wcDocker, pgBrowser = pgAdmin.Browser; @@ -12,7 +14,81 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr) { if (pgAdmin.Dashboard) return; - var dashboardVisible = true; + var dashboardVisible = true, + cancel_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 cancelSessionCell = 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('Cancel Active Query?'), + txtConfirm = gettext('Are you sure you wish to cancel active query?'); + + 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('Active query cancelled successfully.')); + refresh_grid(); + } else { + alertifyWrapper.error(gettext('Error during canceling active query.')); + } + }, + 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() { @@ -73,6 +149,20 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr) { 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']; + 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( @@ -85,10 +175,16 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr) { if ('database' in treeHierarchy) { sid = treeHierarchy.server._id; did = treeHierarchy.database._id; + is_server_dashboard = false; + is_database_dashboard = true; url += sid + '/' + did; + cancel_session_url += sid + '/' + did + '/'; } else if ('server' in treeHierarchy) { sid = treeHierarchy.server._id; + is_server_dashboard = true; + is_database_dashboard = false; url += sid; + cancel_session_url += sid + '/'; } } @@ -486,6 +582,15 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr) { }]); } + // Add cancel active query button + server_activity_columns.unshift({ + name: "pg-backform-delete", label: "", + cell: cancelSessionCell, + editable: false, cell_priority: -1, + canDeleteRow: pgAdmin.Dashboard.can_cancel_active_query, + postgres_version: version + }); + var server_locks_columns = [{ name: "pid", label: gettext('PID'), @@ -790,6 +895,14 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr) { }]); } + database_activity_columns.unshift({ + name: "pg-backform-delete", label: "", + cell: cancelSessionCell, + editable: false, cell_priority: -1, + canDeleteRow: pgAdmin.Dashboard.can_cancel_active_query, + postgres_version: version + }); + var database_locks_columns = [{ name: "pid", label: gettext('PID'), @@ -954,6 +1067,52 @@ function(url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, Flotr) { }, toggleVisibility: function(flag) { dashboardVisible = flag; + }, + can_cancel_active_query: 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'}), + alertifyWrapper = new AlertifyWrapper(), + 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')) { + alertifyWrapper.info( + gettext('You cannot cancel the postgres\'s internal session.') + ); + 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) { + alertifyWrapper.error( + gettext('You are not allowed to cancel the main active session.') + ); + return false; + } else if(m.get('state') == 'idle') { + // If this session is already idle then do nothing + alertifyWrapper.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 + var alertifyWrapper = new AlertifyWrapper(); + alertifyWrapper.error( + gettext('You do not have required account privileges.') + ); + return false; + } } }; diff --git a/web/pgadmin/static/css/bootstrap.overrides.css b/web/pgadmin/static/css/bootstrap.overrides.css index a590aa7..6274c74 100755 --- a/web/pgadmin/static/css/bootstrap.overrides.css +++ b/web/pgadmin/static/css/bootstrap.overrides.css @@ -71,7 +71,7 @@ iframe { } /* - * Bootstrap 3 remove submenus as they don't work overly well on Mobile. The + * Bootstrap 3 remove submenus as they don't work overly well on Mobile. The * following CSS adds them back in - for our purposes they actually work fine * on Mobile and are perfectly responsive */ @@ -570,7 +570,7 @@ fieldset[disabled] .form-control { } .backgrid.presentation td.renderable { - padding: 3px; + padding: 6px 3px 3px 3px; font-size: 12px; } @@ -984,7 +984,7 @@ ul.nav.nav-tabs { background-position: 0px 2px; } -/* This rule will stop Chrome apply highlighting to elements such as DIV's used as modals */ +/* This rule will stop Chrome apply highlighting to elements such as DIV's used as modals */ *:focus { outline: none; } @@ -993,7 +993,7 @@ ul.nav.nav-tabs { .alert-info-panel { border: 2px solid #a1a1a1; margin-top: 2em; - padding: 5px 5px; + padding: 5px 5px; background: #dddddd; border-radius: 5px; height: 8em; @@ -1334,4 +1334,4 @@ body { color: #333; font-size: 14px; font-weight: normal; -} \ No newline at end of file +} diff --git a/web/pgadmin/static/js/alerts/alertify_wrapper.js b/web/pgadmin/static/js/alerts/alertify_wrapper.js index d3e7ed6..10d5f60 100644 --- a/web/pgadmin/static/js/alerts/alertify_wrapper.js +++ b/web/pgadmin/static/js/alerts/alertify_wrapper.js @@ -31,10 +31,25 @@ define([ return alert; }; + var info = function(message, timeout) { + var alertMessage = '\ +
\ +
\ +
\ + \ +
\ +
' + message + '
\ +
\ +
'; + var alert = alertify.notify(alertMessage, timeout); + return alert; + }; + $.extend(this, { 'success': success, 'error': error, + 'info': info, }); }; return AlertifyWrapper; -}); \ No newline at end of file +}); diff --git a/web/pgadmin/static/scss/_alert.scss b/web/pgadmin/static/scss/_alert.scss index 28b5f2a..fdd4546 100644 --- a/web/pgadmin/static/scss/_alert.scss +++ b/web/pgadmin/static/scss/_alert.scss @@ -110,6 +110,7 @@ category: alerts width: 50px; height: 50px; font-size: 14px; + text-align: center; } .success-icon { @@ -120,36 +121,8 @@ category: alerts background: #d0021b; } -.alert-text { - display: inline-block; - padding: 0 12px 0 10px; -} - -.alert-row { - display: block; -} - -.alert-box { - padding: 0px; - display: inline-block; -} - -.alert-icon { - display: inline-block; - color: white; - padding: 15px; - width: 50px; - height: 50px; - font-size: 14px; - text-align: center; -} - -.success-icon { - background: #3a773a; -} - -.error-icon { - background: #d0021b; +.info-icon { + background: #2c76b4; } .alert-text { diff --git a/web/pgadmin/static/scss/_alertify.overrides.scss b/web/pgadmin/static/scss/_alertify.overrides.scss index f1987f9..8344956 100644 --- a/web/pgadmin/static/scss/_alertify.overrides.scss +++ b/web/pgadmin/static/scss/_alertify.overrides.scss @@ -170,6 +170,12 @@ button.pg-alertify-button { @extend .ajs-text-smoothing; } +.ajs-message.ajs-visible { + @extend .bg-blue-1; + @extend .border-blue-2; + @extend .ajs-text-smoothing; +} + .media-body { display: table-row; -} \ No newline at end of file +}