Hi Dave, Please find updated patch.
On Tue, Jul 18, 2017 at 8:05 PM, Murtuza Zabuawala <murtuza.zabuawala@ enterprisedb.com> wrote: > Hi Shirley, > > On Tue, Jul 18, 2017 at 1:21 AM, Shirley Wang <sw...@pivotal.io> wrote: > >> Hi! >> >> I can't seem to get the patch to completely work on my computer, only the >> close icon shows up plus the dialog and success/error messages, but here >> are some comments: >> >> Because we are just cancelling the active running query, so if the start > of the session is 'Active' when you cancel it will simply goto 'Idle' stat. > >> +1 to Dave's comment about refreshing after the cancel operation >> >> I'll fix this. > >> - We're working on a patch for updating alerts in the Dashboard tab which >> updates the grays in the Database activities panel and changes the border >> around the refresh button and search bar to 1px. This hasn't been submitted >> yet but just a heads up as you work on the alignment. >> >> - Something to consider is how a super user will identify which session >> should be closed. Is that information there? >> >> I think super user can cancel everything except main connection session & > as Dave mentioned in previous email that background workers in PG10. > >> - Are there sessions that should never be closed? If so, do they also >> need close buttons? (Probably not, because that will lead the user to an >> error message, which is not fun) >> >> In Backgrid, we can not exclude specific column from certain rows if it > renders in one row then it will render for every row in the grid, What we > can do is, it will throw an error when user is not eligible to cancel the > active running query. > >> - Perhaps this is a good feature to review with Chethana! :) >> >> On Mon, Jul 17, 2017 at 5:37 AM Murtuza Zabuawala < >> murtuza.zabuaw...@enterprisedb.com> wrote: >> >>> On Mon, Jul 17, 2017 at 3:01 PM, Dave Page <dp...@pgadmin.org> wrote: >>> >>>> Hi >>>> >>>> On Thu, Jul 13, 2017 at 2:53 PM, Murtuza Zabuawala < >>>> murtuza.zabuaw...@enterprisedb.com> wrote: >>>> >>>>> 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" >>>>> >>>>> Some comments: >>>> >>>> - The action here is to cancel the active query in the backend, not the >>>> session - so messages etc. should say things like "Cancel Active Query?" >>>> >>>> >>> - The grid should refresh following the cancel operation. >>>> >>>> - Can you fix the vertical alignment while you're working on this? The >>>> new button really makes the poor alignment stand out. >>>> >>>> - This should not be superuser only - regular users should be able to >>>> cancel their own queries. >>>> >>>> - On PG10, background workers are also shown in the dashboard. Should >>>> we prevent attempts to cancel their work (they'll fail anyway I believe). >>>> >>>> Thanks! >>>> >>>> >>> Sure, I'll work on these comments & send updated patch. >>> >>>> -- >>>> Dave Page >>>> Blog: http://pgsnake.blogspot.com >>>> Twitter: @pgsnake >>>> >>>> EnterpriseDB UK: http://www.enterprisedb.com >>>> The Enterprise PostgreSQL Company >>>> >>> >
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/<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 + """ + 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( + "<i class='fa fa-times-circle' data-toggle='tooltip' " + + "title='" + gettext('Cancel the current active query') + + "'></i>" + ); + 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 = '\ + <div class="media alert-info font-blue text-14">\ + <div class="media-body media-middle">\ + <div class="alert-icon info-icon">\ + <i class="fa fa-info" aria-hidden="true"></i>\ + </div>\ + <div class="alert-text">' + message + '</div>\ + </div>\ + </div>'; + 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 +}