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
+}

Reply via email to