​Hi Dave,

PFA updated patch with additional checks to prevent unnecessary ​polling
added as suggested.
Please review.

--
Regards,
Murtuza Zabuawala
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


On Thu, Jan 11, 2018 at 2:33 PM, Dave Page <dp...@pgadmin.org> wrote:

>
>
> On Thu, Jan 11, 2018 at 7:00 AM, Harshal Dhumal <
> harshal.dhu...@enterprisedb.com> wrote:
>
>> On Thu, Jan 11, 2018 at 12:06 PM, Murtuza Zabuawala <
>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>
>>> User can open Query tool in new browser window where we'll not have
>>> wcDocker panel.
>>>
>>
>> In that case we can use window onfocus and onblur events
>> <http://www.thefutureoftheweb.com/blog/detect-browser-window-focus>.
>> In sqleditor there are cases where we've written conditional code based
>> on whether sqleditor is opened in new window or not.
>>
>
> This is a great suggestion (the idea in general). We need to make this
> happen - it'll go a long way to minimising our concerns about the amount of
> polling.
>
>
>>
>>
>> On Thu, Jan 11, 2018 at 12:00 PM, Harshal Dhumal <
>>> harshal.dhu...@enterprisedb.com> wrote:
>>>
>>>> Murtuza, I think we should only poll if sqleditor/datagrid is visible.
>>>> We've *wcDocker.EVENT.VISIBILITY_CHANGED *event when panel visibility
>>>> changes.
>>>>
>>>> --
>>>> *Harshal Dhumal*
>>>> *Sr. Software Engineer*
>>>>
>>>> EnterpriseDB India: http://www.enterprisedb.com
>>>> The Enterprise PostgreSQL Company
>>>>
>>>> On Wed, Jan 10, 2018 at 1:55 PM, Murtuza Zabuawala <
>>>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>>>
>>>>> Hi Dave,
>>>>>
>>>>> PFA updated patch.
>>>>>
>>>>> On Tue, Jan 9, 2018 at 7:57 PM, Dave Page <dp...@pgadmin.org> wrote:
>>>>>
>>>>>> Hi
>>>>>>
>>>>>> On Tue, Jan 9, 2018 at 6:33 AM, Murtuza Zabuawala <
>>>>>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>>>>>
>>>>>>> Hi Dave,
>>>>>>>
>>>>>>> Please find updated patch.
>>>>>>>
>>>>>>
>>>>>> I turned off the status option, but polling is still happening. This
>>>>>> should definitely stop! :-)
>>>>>>
>>>>> ​Fixed typo in variable.
>>>>>
>>>>> Can you also reverse the enable/disable switch and the interval
>>>>>> setting on the preferences page? I think Enable/Disable should be at the
>>>>>> top, and be followed by the interval.
>>>>>> ​
>>>>>>
>>>>>  Fixed.
>>>>> ​
>>>>> But just a heads up we won't be able to put '?' after 'Connection
>>>>> status'​  eg: '
>>>>> Connection status
>>>>> ​?'​
>>>>> ​
>>>>> ​Then string sorting again puts it after 'Connection status refresh
>>>>> rate' ​:)
>>>>>
>>>>>> ​
>>>>>>
>>>>>> Thanks.
>>>>>>
>>>>>>
>>>>>>>
>>>>>>> On Mon, Jan 8, 2018 at 7:21 PM, Dave Page <dp...@pgadmin.org> wrote:
>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On Mon, Jan 8, 2018 at 1:24 PM, Murtuza Zabuawala <
>>>>>>>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>>>>>>>
>>>>>>>>> Hi Dave,
>>>>>>>>>
>>>>>>>>> PFA updated patch.
>>>>>>>>>
>>>>>>>>> On Mon, Jan 8, 2018 at 5:11 PM, Dave Page <dp...@pgadmin.org>
>>>>>>>>> wrote:
>>>>>>>>>
>>>>>>>>>> Hi
>>>>>>>>>>
>>>>>>>>>> On Fri, Jan 5, 2018 at 8:49 AM, Murtuza Zabuawala <
>>>>>>>>>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>>>>>>>>>
>>>>>>>>>>> Hi Dave,
>>>>>>>>>>>
>>>>>>>>>>> PFA updated patch,
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> On Wed, Jan 3, 2018 at 10:44 PM, Dave Page <dp...@pgadmin.org>
>>>>>>>>>>> wrote:
>>>>>>>>>>>
>>>>>>>>>>>> Hi
>>>>>>>>>>>>
>>>>>>>>>>>> On Thu, Dec 28, 2017 at 9:38 AM, Murtuza Zabuawala <
>>>>>>>>>>>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>>>>>>>>>>>
>>>>>>>>>>>>> Hi,
>>>>>>>>>>>>>
>>>>>>>>>>>>> PFA updated patch based on new design suggested by Chethana.
>>>>>>>>>>>>> The patch also includes some misc fixes related to object
>>>>>>>>>>>>> validation.
>>>>>>>>>>>>> RM#2475
>>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> This seems much nicer, but I still think there are some tweaks
>>>>>>>>>>>> to make:
>>>>>>>>>>>>
>>>>>>>>>>>> 1) If I open a query tool, and then stop the application
>>>>>>>>>>>> server, the icon is updated to show the broken connection. 
>>>>>>>>>>>> However, unless
>>>>>>>>>>>> I execute a query in the query tool before the server is shut 
>>>>>>>>>>>> down, the
>>>>>>>>>>>> connection status won't recover when the server is restarted. If I 
>>>>>>>>>>>> do run a
>>>>>>>>>>>> query first (SELECT 1; will do), then the connection status will 
>>>>>>>>>>>> recover.
>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> I have logged​
>>>>>>>>>>> ​
>>>>>>>>>>> https://redmine.postgresql.org/issues/2983
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> 2) I think the "connected" icon should be in $primary-blue
>>>>>>>>>>>> (#2c76b4). The green is ugly and not overly easy to read. It's also
>>>>>>>>>>>> distracting as it catches the eye, which the default, non-error 
>>>>>>>>>>>> state
>>>>>>>>>>>> should not do.
>>>>>>>>>>>>
>>>>>>>>>>> ​Fixed​
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Much better.
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> 3) I'm not overly happy with the the status text popover. After
>>>>>>>>>>>> some thought, I think it's because there are no visual clues that 
>>>>>>>>>>>> you
>>>>>>>>>>>> should click on the icon to see it - and Karen seems to be of a 
>>>>>>>>>>>> similar
>>>>>>>>>>>> opinion. Can we put a small marker there, perhaps a triangle on the
>>>>>>>>>>>> bottom-right, like you get on a spreadsheet cell if you add a 
>>>>>>>>>>>> comment/note?
>>>>>>>>>>>> We should also have a hotkey and I guess a tooltip, e.g. 
>>>>>>>>>>>> "Connection status
>>>>>>>>>>>> Ctrl+Alt+S" or similar.
>>>>>>>>>>>>
>>>>>>>>>>> ​Fixed​
>>>>>>>>>>> , added accesskey *'T'* for TX status tooltip shortcut as 'S'
>>>>>>>>>>> is already taken for Save file option
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Hmm - having seen it, I don't think the marker helps us.
>>>>>>>>>>
>>>>>>>>>> Can you remove it, and fix the tooltip (which doesn't seem to
>>>>>>>>>> work)? If we always have the tooltip say "Connection status 
>>>>>>>>>> Ctrl+Alt+T" (or
>>>>>>>>>> whatever is appropriate for the platform/browser), then that should 
>>>>>>>>>> give
>>>>>>>>>> the user enough hint to click.
>>>>>>>>>>
>>>>>>>>> ​
>>>>>>>>> Fixed​.
>>>>>>>>> I have removed the marker & added tooltip instead but it is not
>>>>>>>>> possible to add specific shortcut keys in tooltip because accesskey 
>>>>>>>>> may
>>>>>>>>> vary depending on OS & browser.
>>>>>>>>> ​Ref: ​
>>>>>>>>> https://www.w3schools.com/tags/att_global_accesskey.asp
>>>>>>>>>
>>>>>>>>
>>>>>>>> That's better - though I think the tool tip is better as something
>>>>>>>> like:
>>>>>>>>
>>>>>>>> Connection status (click for details) (<accesskey>+T)
>>>>>>>>
>>>>>>>> I'm still not overly happy with all the polling that's going on
>>>>>>>> though. It's a lot of requests, especially with multiple QTs open. I 
>>>>>>>> think
>>>>>>>> we need to be able to disable the feature entirely through a switch in 
>>>>>>>> the
>>>>>>>> Preferences. In that case, no icon would be shown, and polling would be
>>>>>>>> disabled - i.e. everything would be as it is now.
>>>>>>>>
>>>>>>>> What do you think?
>>>>>>>>
>>>>>>> ​Fixed
>>>>>>> Made it configurable & set default polling time to 10sec.​
>>>>>>>
>>>>>>>
>>>>>>> ​Please review.​
>>>>>>>
>>>>>>>
>>>>>>>> --
>>>>>>>> Dave Page
>>>>>>>> Blog: http://pgsnake.blogspot.com
>>>>>>>> Twitter: @pgsnake
>>>>>>>>
>>>>>>>> EnterpriseDB UK: http://www.enterprisedb.com
>>>>>>>> The Enterprise PostgreSQL Company
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Dave Page
>>>>>> Blog: http://pgsnake.blogspot.com
>>>>>> Twitter: @pgsnake
>>>>>>
>>>>>> EnterpriseDB UK: http://www.enterprisedb.com
>>>>>> The Enterprise PostgreSQL Company
>>>>>>
>>>>>
>>>>>
>>>>
>>>
>>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>
diff --git a/web/pgadmin/browser/server_groups/servers/__init__.py 
b/web/pgadmin/browser/server_groups/servers/__init__.py
index b2cca09..89ae779 100644
--- a/web/pgadmin/browser/server_groups/servers/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/__init__.py
@@ -886,9 +886,12 @@ class ServerNode(PGChildNodeView):
         if server is None:
             return bad_request(gettext("Server not found."))
 
-        # Fetch User Details.
-        user = User.query.filter_by(id=current_user.id).first()
-        if user is None:
+        if current_user and hasattr(current_user, 'id'):
+            # Fetch User Details.
+            user = User.query.filter_by(id=current_user.id).first()
+            if user is None:
+                return unauthorized(gettext("Unauthorized request."))
+        else:
             return unauthorized(gettext("Unauthorized request."))
 
         data = request.form if request.form else json.loads(
diff --git 
a/web/pgadmin/browser/server_groups/servers/databases/templates/databases/css/database.css
 
b/web/pgadmin/browser/server_groups/servers/databases/templates/databases/css/database.css
index 5300189..f0b12e3 100644
--- 
a/web/pgadmin/browser/server_groups/servers/databases/templates/databases/css/database.css
+++ 
b/web/pgadmin/browser/server_groups/servers/databases/templates/databases/css/database.css
@@ -9,6 +9,6 @@
 
 .icon-database-not-connected {
   background-image: url('{{ url_for('NODE-database.static', 
filename='img/databasebad.svg') }}') !important;
-  border-radius: 10px
+  border-radius: 10px;
   background-size: 20px !important;
 }
diff --git a/web/pgadmin/static/css/bootstrap.overrides.css 
b/web/pgadmin/static/css/bootstrap.overrides.css
index b0c531a..d2936a7 100755
--- a/web/pgadmin/static/css/bootstrap.overrides.css
+++ b/web/pgadmin/static/css/bootstrap.overrides.css
@@ -1438,3 +1438,9 @@ body {
 .multi-checkbox .check.partial:after {
   content: "\003F";
 }
+
+/* Override default bootstrap popover fonts & size */
+.popover-content {
+  font-family: 'Open Sans';
+  font-size: 13px;
+}
diff --git a/web/pgadmin/static/js/sqleditor_utils.js 
b/web/pgadmin/static/js/sqleditor_utils.js
index 21d3185..1d717e7 100644
--- a/web/pgadmin/static/js/sqleditor_utils.js
+++ b/web/pgadmin/static/js/sqleditor_utils.js
@@ -8,8 +8,8 @@
 //////////////////////////////////////////////////////////////////////////
 // This file contains common utilities functions used in sqleditor modules
 
-define(['jquery'],
-  function ($) {
+define(['jquery', 'sources/gettext'],
+  function ($, gettext) {
     var sqlEditorUtils = {
       /* Reference link 
http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
        * Modified as per requirement.
@@ -56,6 +56,143 @@ define(['jquery'],
       capitalizeFirstLetter: function (string) {
         return string.charAt(0).toUpperCase() + string.slice(1);
       },
+
+      // Status flag
+      previousStatus: null,
+
+      // This function will fetch the connection status via ajax
+      fetchConnectionStatus: function(url, $el, $status_el) {
+        // If user has switch the browser Tab or Minimized window or
+        // if wcDocker panel is not in focus then don't fire AJAX
+        if (document.visibilityState !== 'visible' ||
+              $el.data('panel-visible') !== 'visible' ) {
+          return;
+        }
+        // Start polling..
+        $.ajax({
+          url: url,
+          method: 'GET',
+          success: function (res) {
+            if(res && res.data) {
+              var status = res.data.status,
+                msg = res.data.message,
+                is_status_changed = false;
+
+              // Inject CSS as required
+              switch(status) {
+                  // Busy
+                  case 1:
+                      // if received busy status more than once then only
+                      if(status == sqlEditorUtils.previousStatus &&
+                          !$status_el.hasClass('fa-hourglass-half')) {
+                        $status_el.removeClass()
+                                  .addClass('fa fa-hourglass-half');
+                        is_status_changed = true;
+                      }
+                      break;
+                  // Idle in transaction
+                  case 2:
+                      if(sqlEditorUtils.previousStatus != status &&
+                          !$status_el.hasClass('fa-clock-o')) {
+                        $status_el.removeClass()
+                                  .addClass('fa fa-clock-o');
+                        is_status_changed = true;
+                      }
+                      break;
+                  // Failed in transaction
+                  case 3:
+                      if(sqlEditorUtils.previousStatus != status &&
+                          !$status_el.hasClass('fa-exclamation-circle')) {
+                        $status_el.removeClass()
+                                  .addClass('fa fa-exclamation-circle');
+                        is_status_changed = true;
+                      }
+                      break;
+                  // Failed in transaction with unknown server side error
+                  case 4:
+                      if(sqlEditorUtils.previousStatus != status &&
+                          !$status_el.hasClass('fa-exclamation-triangle')) {
+                        $status_el.removeClass()
+                                  .addClass('fa fa-exclamation-triangle');
+                        is_status_changed = true;
+                      }
+                      break;
+                  default:
+                      if(sqlEditorUtils.previousStatus != status &&
+                          !$status_el.hasClass('fa-query_tool_connected')) {
+                        $status_el.removeClass()
+                                  .addClass('fa-custom 
fa-query-tool-connected');
+                        is_status_changed = true;
+                      }
+              }
+
+              sqlEditorUtils.previousStatus = status;
+              // Set bootstrap popover message
+              if(is_status_changed) {
+                $el.popover('hide');
+                $el.attr('data-content', msg);
+              }
+            } else {
+              // We come here means we did not receive expected response
+              // from server, we need to error out
+              sqlEditorUtils.previousStatus = -99;
+              msg = gettext("Something went wrong, " +
+                            "make sure you are logged into pgAdmin4.");
+              $el.attr('data-content', msg);
+              if(!$status_el.hasClass('fa-query-tool-disconnected')) {
+                $el.popover('hide');
+                $status_el.removeClass()
+                          .addClass('fa-custom fa-query-tool-disconnected');
+              }
+            }
+          },
+          error: function (e) {
+            sqlEditorUtils.previousStatus = -1;
+            var msg = gettext("Transaction status check failed.");
+            if (e.readyState == 0) {
+              msg = gettext("Not connected to the server or the connection to 
" +
+                            "the server has been closed.");
+            } else if (e.responseJSON && e.responseJSON.errormsg) {
+              msg = e.responseJSON.errormsg;
+            }
+
+            // Set bootstrap popover
+            $el.attr('data-content', msg);
+            // Add error class
+            if(!$status_el.hasClass('fa-query-tool-disconnected')) {
+              $el.popover('hide');
+              $status_el.removeClass()
+                        .addClass('fa-custom fa-query-tool-disconnected');
+            }
+          }
+        });
+      },
+
+      // This function will update the connection status
+      updateConnectionStatus: function(url, poll_time) {
+        var $el = $(".connection_status"),
+          $status_el = $($($el).find(".fa-custom"));
+
+        // Apply popover on element
+        $el.popover();
+
+        // To set initial connection status
+        sqlEditorUtils.fetchConnectionStatus(url, $el, $status_el);
+
+        // Calling it again in specified interval
+        setInterval(
+          sqlEditorUtils.fetchConnectionStatus.bind(null, url, $el, 
$status_el),
+          poll_time * 1000
+        );
+      },
+
+      // Updates the flag for connection status poll
+      updateConnectionStatusFlag: function(status) {
+        var $el = $(".connection_status");
+        if ($el.data('panel-visible') != status) {
+          $el.data('panel-visible', status);
+        }
+      },
     };
     return sqlEditorUtils;
   });
diff --git a/web/pgadmin/tools/datagrid/__init__.py 
b/web/pgadmin/tools/datagrid/__init__.py
index 726a0ff..fbd63e6 100644
--- a/web/pgadmin/tools/datagrid/__init__.py
+++ b/web/pgadmin/tools/datagrid/__init__.py
@@ -149,10 +149,11 @@ def initialize_datagrid(cmd_type, obj_type, sid, did, 
obj_id):
     else:
         sql_grid_data = session['gridData']
 
-    # Use pickle to store the command object which will be used
-    # later by the sql grid module.
+    # Use pickle to store the command object which will be used later by the
+    # sql grid module.
     sql_grid_data[trans_id] = {
-        'command_obj': pickle.dumps(command_obj, -1)  # -1 specify the highest 
protocol version available
+        # -1 specify the highest protocol version available
+        'command_obj': pickle.dumps(command_obj, -1)
     }
 
     # Store the grid dictionary into the session variable
@@ -195,15 +196,20 @@ def panel(trans_id, is_query_tool, editor_title):
     user_agent = UserAgent(request.headers.get('User-Agent'))
 
     """
-    Animations and transitions are not automatically GPU accelerated and by 
default use browser's slow rendering engine.
-    We need to set 'translate3d' value of '-webkit-transform' property in 
order to use GPU.
-    After applying this property under linux, Webkit calculates wrong position 
of the elements so panel contents are not visible.
-    To make it work, we need to explicitly set '-webkit-transform' property to 
'none' for .ajs-notifier, .ajs-message, .ajs-modal classes.
-
-    This issue is only with linux runtime application and observed in Query 
tool and debugger.
-    When we open 'Open File' dialog then whole Query-tool panel content is not 
visible though it contains HTML element in back end.
-
-    The port number should have already been set by the runtime if we're 
running in desktop mode.
+    Animations and transitions are not automatically GPU accelerated and by
+    default use browser's slow rendering engine. We need to set 'translate3d'
+    value of '-webkit-transform' property in order to use GPU. After applying
+    this property under linux, Webkit calculates wrong position of the elements
+    so panel contents are not visible. To make it work, we need to explicitly
+    set '-webkit-transform' property to 'none' for .ajs-notifier,
+    .ajs-message, .ajs-modal classes.
+
+    This issue is only with linux runtime application and observed in Query
+    tool and debugger. When we open 'Open File' dialog then whole Query tool
+    panel content is not visible though it contains HTML element in back end.
+
+    The port number should have already been set by the runtime if we're
+    running in desktop mode.
     """
     is_linux_platform = False
 
@@ -218,15 +224,18 @@ def panel(trans_id, is_query_tool, editor_title):
         new_browser_tab = 'false'
 
     if is_query_tool == 'true':
-        prompt_save_changes = 
pref.preference('prompt_save_query_changes').get()
+        prompt_save_changes = pref.preference(
+            'prompt_save_query_changes'
+        ).get()
     else:
         prompt_save_changes = pref.preference('prompt_save_data_changes').get()
 
+    display_connection_status = pref.preference('connection_status').get()
+
     # Fetch the server details
-    #
     bgcolor = None
     fgcolor = None
-    if str(trans_id) in session['gridData']:
+    if 'gridData' in session and str(trans_id) in session['gridData']:
         # Fetch the object for the specified transaction id.
         # Use pickle.loads function to get the command object
         session_obj = session['gridData'][str(trans_id)]
@@ -240,9 +249,12 @@ def panel(trans_id, is_query_tool, editor_title):
             fgcolor = s.fgcolor or 'black'
 
     return render_template(
-        "datagrid/index.html", _=gettext, uniqueId=trans_id,
+        "datagrid/index.html",
+        _=gettext,
+        uniqueId=trans_id,
         is_query_tool=is_query_tool,
-        editor_title=editor_title, script_type_url=sURL,
+        editor_title=editor_title,
+        script_type_url=sURL,
         is_desktop_mode=app.PGADMIN_RUNTIME,
         is_linux=is_linux_platform,
         is_new_browser_tab=new_browser_tab,
@@ -252,7 +264,8 @@ def panel(trans_id, is_query_tool, editor_title):
         fgcolor=fgcolor,
         # convert python boolean value to equivalent js boolean literal before
         # passing it to html template.
-        prompt_save_changes='true' if prompt_save_changes else 'false'
+        prompt_save_changes='true' if prompt_save_changes else 'false',
+        display_connection_status=display_connection_status
     )
 
 
diff --git a/web/pgadmin/tools/datagrid/templates/datagrid/index.html 
b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
index ff4368d..5494fee 100644
--- a/web/pgadmin/tools/datagrid/templates/datagrid/index.html
+++ b/web/pgadmin/tools/datagrid/templates/datagrid/index.html
@@ -1,8 +1,5 @@
 {% extends "base.html" %}
 {% block title %}{{ config.APP_NAME }} - Datagrid{% endblock %}
-{% block css_link %}
-<link type="text/css" rel="stylesheet" href="{{ url_for('sqleditor.static', 
filename='css/sqleditor.css') }}">
-{% endblock %}
 {% block body %}
 <style>
     body {padding: 0px;}
@@ -13,6 +10,14 @@
     .alertify .ajs-dialog.ajs-shake{-webkit-animation-name: none;}
     .sql-editor-busy-icon.fa-pulse{-webkit-animation: none;}
     {% endif %}
+
+    {# Note: If we will display connection status then we have to provide some
+    space to display status icon else we can use all the space available #}
+    .editor-title {
+      width:{% if display_connection_status -%} calc(100% - 43px)
+    {% else %} 100% {%- endif %};
+    }
+
 </style>
 <div id="main-editor_panel">
     <div id="fetching_data" class="wcLoadingIconContainer 
sql-editor-busy-fetching hide">
@@ -284,7 +289,26 @@
                 </button>
             </div>
         </div>
-        <div class="editor-title" style="background-color: {% if fgcolor %}{{ 
bgcolor or '#FFFFFF' }}{% else %}{{ bgcolor or '#2C76B4' }}{% endif %}; color: 
{{ fgcolor or 'white' }};"></div>
+
+        <div class="connection_status_wrapper">
+            {% if display_connection_status %}
+            <div style="display: inline-block;"
+                 title="{{ _('Click here to see detailed connection status 
(<accesskey> + t)') }}">
+                <div class="connection_status" data-container="body"
+                     data-toggle="popover" data-placement="bottom"
+                     data-content=""
+                     data-panel-visible="visible"
+                     accesskey="t">
+                    <i class="fa-custom fa-query-tool-disconnected" 
aria-hidden="true"
+                        title="{{ _('Connection status (click for details) 
(<accesskey>+T)') }}">
+                    </i>
+                </div>
+            </div>
+            {% endif %}
+            <div class="editor-title"
+                 style="background-color: {% if fgcolor %}{{ bgcolor or 
'#FFFFFF' }}{% else %}{{ bgcolor or '#2C76B4' }}{% endif %}; color: {{ fgcolor 
or 'white' }};"></div>
+        </div>
+
         <div id="filter" class="filter-container hidden">
             <div class="filter-title">Filter</div>
             <div class="sql-textarea">
@@ -368,7 +392,13 @@
 {% endif %}
 
     // Start the query tool.
-    sqlEditorController.start({{ is_query_tool }}, "{{ editor_title }}",
-                    script_sql, {{ is_new_browser_tab }}, "{{ server_type }}", 
{{ prompt_save_changes }});
+    sqlEditorController.start(
+        {{ is_query_tool }},
+        "{{ editor_title }}",
+        script_sql,
+        {{ is_new_browser_tab }},
+        "{{ server_type }}",
+        {{ prompt_save_changes }}
+    );
 });
 {% endblock %}
diff --git a/web/pgadmin/tools/sqleditor/__init__.py 
b/web/pgadmin/tools/sqleditor/__init__.py
index aeca5cc..2c8bf36 100644
--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -21,7 +21,7 @@ from pgadmin.tools.sqleditor.command import QueryToolCommand
 from pgadmin.utils import PgAdminModule
 from pgadmin.utils import get_storage_directory
 from pgadmin.utils.ajax import make_json_response, bad_request, \
-    success_return, internal_server_error
+    success_return, internal_server_error, unauthorized
 from pgadmin.utils.driver import get_driver
 from pgadmin.utils.sqlautocomplete.autocomplete import SQLAutoComplete
 from pgadmin.misc.file_manager import Filemanager
@@ -50,6 +50,14 @@ TX_STATUS__ACTIVE = 1
 TX_STATUS_INTRANS = 2
 TX_STATUS_INERROR = 3
 
+# Connection status codes mapping
+CONNECTION_STATUS_MESSAGE_MAPPING = dict({
+    0: 'The session is idle and there is no current transaction.',
+    1: 'A command is currently in progress.',
+    2: 'The session is idle in a valid transaction block.',
+    3: 'The session is idle in a failed transaction block.',
+    4: 'The connection with the server is bad.'
+})
 
 class SqlEditorModule(PgAdminModule):
     """
@@ -107,7 +115,8 @@ class SqlEditorModule(PgAdminModule):
             'sqleditor.autocomplete',
             'sqleditor.load_file',
             'sqleditor.save_file',
-            'sqleditor.query_tool_download'
+            'sqleditor.query_tool_download',
+            'sqleditor.connection_status'
         ]
 
     def register_preferences(self):
@@ -333,6 +342,24 @@ class SqlEditorModule(PgAdminModule):
             }
         )
 
+        self.display_connection_status = self.preference.register(
+            'display', 'connection_status',
+            gettext("Connection status"), 'boolean', True,
+            category_label=gettext('Display'),
+            help_str=gettext('If set to True, the Query Tool '
+                             'will display status of connection status and '
+                             'transaction status')
+        )
+
+        self.connection_status = self.preference.register(
+            'display', 'connection_status_fetch_time',
+            gettext("Connection status refresh rate"), 'integer', 10,
+            min_val=1, max_val=600,
+            category_label=gettext('Display'),
+            help_str=gettext('The number of seconds between connection '
+                             'status polling.')
+        )
+
 blueprint = SqlEditorModule(MODULE_NAME, __name__, static_url_path='/static')
 
 
@@ -345,10 +372,10 @@ def index():
 
 
 def update_session_grid_transaction(trans_id, data):
-    grid_data = session['gridData']
-    grid_data[str(trans_id)] = data
-
-    session['gridData'] = grid_data
+    if 'gridData' in session:
+        grid_data = session['gridData']
+        grid_data[str(trans_id)] = data
+        session['gridData'] = grid_data
 
 
 def check_transaction_status(trans_id):
@@ -363,6 +390,10 @@ def check_transaction_status(trans_id):
     Returns: status and connection object
 
     """
+    if 'gridData' not in session:
+        return False, unauthorized(gettext("Unauthorized request.")), \
+               None, None, None
+
     grid_data = session['gridData']
 
     # Return from the function if transaction id not found
@@ -497,13 +528,23 @@ def start_query_tool(trans_id):
     else:
         sql = request.args or request.form
 
+    if 'gridData' not in session:
+        return make_json_response(
+            data={
+                'status': False,
+                'result': gettext('Transaction ID not found in the session.'),
+                'can_edit': False, 'can_filter': False
+            }
+        )
+
     grid_data = session['gridData']
 
     # Return from the function if transaction id not found
     if str(trans_id) not in grid_data:
         return make_json_response(
             data={
-                'status': False, 'result': gettext('Transaction ID not found 
in the session.'),
+                'status': False,
+                'result': gettext('Transaction ID not found in the session.'),
                 'can_edit': False, 'can_filter': False
             }
         )
@@ -1809,3 +1850,50 @@ def start_query_download_tool(trans_id):
         return internal_server_error(
             errormsg=gettext("Transaction status check failed.")
         )
+
+@blueprint.route(
+    '/status/<int:trans_id>',
+    methods=["GET"],
+    endpoint='connection_status'
+)
+@login_required
+def query_tool_status(trans_id):
+    """
+    The task of this function to return the status of the current connection
+    used in query tool instance with given transaction ID.
+    Args:
+        trans_id: Transaction ID
+
+    Returns:
+        Response with the connection status
+
+        Psycopg2 Status Code Mapping:
+        -----------------------------
+        TRANSACTION_STATUS_IDLE     = 0
+        TRANSACTION_STATUS_ACTIVE   = 1
+        TRANSACTION_STATUS_INTRANS  = 2
+        TRANSACTION_STATUS_INERROR  = 3
+        TRANSACTION_STATUS_UNKNOWN  = 4
+    """
+    status, error_msg, conn, trans_obj, \
+        session_obj = check_transaction_status(trans_id)
+
+    if not status and error_msg and type(error_msg) == str:
+        return internal_server_error(
+            errormsg=error_msg
+        )
+
+    if conn and trans_obj and session_obj:
+        status = conn.conn.get_transaction_status()
+        return make_json_response(
+            data={
+                'status': status,
+                'message': gettext(
+                    CONNECTION_STATUS_MESSAGE_MAPPING.get(status)
+                )
+            }
+        )
+    else:
+        return internal_server_error(
+            errormsg=gettext("Transaction status check failed.")
+        )
diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css 
b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
index 782d3e7..7088f8e 100644
--- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
+++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css
@@ -14,7 +14,7 @@
 .sql-editor-busy-fetching {
   position:absolute;
   left: 0;
-  top: 41px;
+  top: 65px;
   bottom: 0;
   right: 0;
   margin:0;
@@ -33,11 +33,6 @@
   z-index: 0;
 }
 
-.editor-title {
-  padding: 4px 5px;
-  font-size: 13px;
-}
-
 .sql-editor-grid-container {
   height: 100%;
   overflow: auto;
@@ -524,3 +519,56 @@ input.editor-checkbox:focus {
 #datagrid .slick-row .slick-cell {
   white-space: pre;
 }
+
+/* CSS for connection status icon */
+#output-panel .CodeMirror {
+  border-top: 1px solid #ddd;
+}
+
+.connection_status_wrapper {
+  width: 100%;
+  background-color: #f7f7f7;
+}
+
+.editor-title {
+  padding: 4px 5px;
+  font-size: 13px;
+  display: inline-block;
+}
+
+.connection_status {
+  width: 11px;
+  position: relative;
+  display: inline-block;
+  margin-top: 2px;
+  margin-left: 12px;
+  margin-right: 16px;
+}
+
+.connection_status .fa {
+  font-size: 16px;
+  margin-left: 2px;
+}
+
+.connection_status .fa-clock-o,
+.connection_status .fa-hourglass-half {
+  color: #e8a735;
+}
+
+.connection_status .fa-exclamation-circle,
+.connection_status .fa-exclamation-triangle {
+  color: #d0021b;
+}
+
+.connection_status .fa-custom {
+  height: 18px;
+  margin-bottom: -4px;
+}
+
+.connection_status .fa-query-tool-connected {
+  content: url('../img/connect.svg');
+}
+
+.connection_status .fa-query-tool-disconnected {
+  content: url('../img/disconnect.svg');
+}
diff --git a/web/pgadmin/tools/sqleditor/static/img/connect.svg 
b/web/pgadmin/tools/sqleditor/static/img/connect.svg
new file mode 100644
index 0000000..9170fc9
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/static/img/connect.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"; 
viewBox="0 0 24 
24"><defs><style>.cls-1{fill:#2c76b4;}</style></defs><title>connect</title><path
 class="cls-1" 
d="M21.27,4.25c.12-.12.12-.18,0-.29-.32-.3-.63-.61-.92-.93-.12-.13-.19-.13-.31,0-.69.71-1.4,1.4-2.1,2.11L17.7,4.9a4.22,4.22,0,0,0-5.26-.82,9.75,9.75,0,0,0-1.69,1.46l-.58.55c-.18-.18-.36-.35-.53-.53s-.14-.1-.23,0c-.31.33-.63.65-1,1-.12.12-.13.18,0,.3.69.67,1.36,1.37,2.05,2,.15.14.13.21,0,.34l-2.05,2c-.12.12-.12.18,0,.29.32.3.63.61.92.93.12.13.19.13.31,0l2-2.05c.12-.13.19-.15.33,0,.61.63,1.24,1.25,1.87,1.87.13.12.12.19,0,.31l-2.07,2.06c-.11.11-.13.17,0,.29q.47.44.91.91c.13.14.2.16.35,0,.67-.69,1.36-1.36,2-2.05.12-.13.19-.12.31,0l2.08,2.09c.09.1.15.12.25,0,.32-.34.66-.67,1-1,.1-.1.07-.15,0-.23l-.61-.6c.48-.45,1-.86,1.41-1.32a4.13,4.13,0,0,0,1.2-3.67,4,4,0,0,0-1.42-2.55c-.14-.12-.14-.19,0-.32C20,5.56,20.61,4.9,21.27,4.25Zm-3.05,7.41c-.43.39-.81.83-1.2,1.23L11.37,7.25l1.42-1.34a2.52,2.52,0,0,1,3.48-.06C17,6.55,17.74,7.27,18.43,8A2.51,2.51,0,0,1,18.22,11.67Z"/><path
 class="cls-1" 
d="M15.52,17.16c-2-1.95-8-8-9-9C6.4,8,6.34,8,6.22,8.14c-.3.32-.62.64-.94.94-.11.11-.14.17,0,.28s.35.33.6.57c-.49.45-1,.88-1.42,1.34a4.16,4.16,0,0,0-1.12,4,4.37,4.37,0,0,0,1.33,2.22c.13.13.13.2,0,.33-.65.63-1.28,1.28-1.93,1.91-.13.13-.12.19,0,.31.32.3.62.61.92.92.11.12.18.12.29,0,.53-.55,1.07-1.08,1.62-1.62.15-.15.29-.42.47-.41s.31.25.47.39a4.21,4.21,0,0,0,5.81,0c.5-.47.94-1,1.43-1.53.25.26.43.44.6.63s.14.11.24,0c.3-.32.61-.63.93-.92C15.67,17.38,15.68,17.31,15.52,17.16Zm-3-.38c-.43.43-.84.87-1.27,1.29a2.53,2.53,0,0,1-3.74-.17c-.53-.57-1.1-1.11-1.66-1.64a2.58,2.58,0,0,1,0-3.93c.41-.41.79-.84,1.19-1.27l5.52,5.52C12.57,16.66,12.58,16.7,12.49,16.78Z"/></svg>
diff --git a/web/pgadmin/tools/sqleditor/static/img/disconnect.svg 
b/web/pgadmin/tools/sqleditor/static/img/disconnect.svg
new file mode 100644
index 0000000..113b758
--- /dev/null
+++ b/web/pgadmin/tools/sqleditor/static/img/disconnect.svg
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg"; 
viewBox="0 0 24 
24"><defs><style>.cls-1{fill:#d0021b;}</style></defs><title>disconnect</title><path
 class="cls-1" 
d="M22.49,2.73c.12-.11.12-.18,0-.29-.31-.3-.62-.6-.91-.91-.12-.13-.18-.13-.31,0-.68.69-1.37,1.38-2.07,2.07L19,3.36a4.15,4.15,0,0,0-5.18-.81A9.59,9.59,0,0,0,12.14,4l-.57.54c-.18-.17-.36-.34-.53-.52s-.13-.09-.22,0c-.31.32-.62.64-.95.94-.12.11-.13.18,0,.3.68.66,1.34,1.34,2,2,.14.14.12.21,0,.34l-2,2c-.12.11-.12.18,0,.29.31.29.62.6.91.91.12.13.18.12.3,0l2-2c.12-.13.19-.15.32,0,.6.62,1.22,1.23,1.84,1.84.12.12.12.18,0,.3l-2,2c-.11.11-.13.17,0,.29q.46.43.89.89c.13.13.2.15.34,0,.66-.68,1.34-1.34,2-2,.12-.13.18-.12.3,0l2,2.05c.09.09.14.11.24,0,.32-.33.65-.66,1-1,.1-.09.07-.14,0-.22l-.6-.59c.47-.44.95-.85,1.38-1.3A4.06,4.06,0,0,0,22,7.47,4,4,0,0,0,20.56,5c-.14-.12-.14-.18,0-.31C21.2,4,21.84,3.36,22.49,2.73Zm-3,7.29c-.42.38-.8.81-1.18,1.21L12.75,5.68l1.39-1.32a2.48,2.48,0,0,1,3.42-.06C18.3,5,19,5.7,19.69,6.44A2.47,2.47,0,0,1,19.48,10Z"/><path
 class="cls-1" 
d="M14.1,18.75c-1.93-1.92-7.84-7.83-8.87-8.87-.11-.11-.16-.13-.28,0-.3.32-.61.63-.93.93-.11.1-.14.17,0,.28s.35.33.59.56c-.48.45-1,.86-1.4,1.32a4.09,4.09,0,0,0-1.1,3.93,4.3,4.3,0,0,0,1.31,2.19c.13.13.13.2,0,.33-.64.62-1.26,1.26-1.9,1.88-.13.12-.12.19,0,.3.31.29.61.6.91.91.11.12.17.12.29,0,.52-.54,1.06-1.07,1.59-1.59.15-.15.28-.41.46-.41s.31.24.46.38a4.14,4.14,0,0,0,5.72,0c.5-.46.92-1,1.4-1.51.25.26.42.43.59.62s.14.11.24,0c.3-.31.6-.62.91-.91C14.24,19,14.25,18.9,14.1,18.75Zm-3-.37c-.42.42-.82.86-1.25,1.27a2.49,2.49,0,0,1-3.68-.17c-.52-.56-1.08-1.09-1.64-1.62a2.54,2.54,0,0,1,0-3.87c.4-.4.78-.83,1.17-1.25l5.43,5.43C11.2,18.25,11.21,18.29,11.12,18.38Z"/></svg>
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js 
b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index 40c975c..590996b 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -107,6 +107,8 @@ define('tools.querytool', [
         filter = self.$el.find('#sql_filter');
 
       $('.editor-title').text(_.unescape(self.editor_title));
+      self.checkConnectionStatus();
+
       self.filter_obj = CodeMirror.fromTextArea(filter.get(0), {
         lineNumbers: true,
         mode: self.handler.server_type === 'gpdb' ? 'text/x-gpsql' : 
'text/x-pgsql',
@@ -114,8 +116,12 @@ define('tools.querytool', [
           widget: '\u2026',
         },
         foldGutter: {
-          rangeFinder: 
CodeMirror.fold.combine(CodeMirror.pgadminBeginRangeFinder, 
CodeMirror.pgadminIfRangeFinder,
-            CodeMirror.pgadminLoopRangeFinder, 
CodeMirror.pgadminCaseRangeFinder),
+          rangeFinder: CodeMirror.fold.combine(
+            CodeMirror.pgadminBeginRangeFinder,
+            CodeMirror.pgadminIfRangeFinder,
+            CodeMirror.pgadminLoopRangeFinder,
+            CodeMirror.pgadminCaseRangeFinder
+          ),
         },
         gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
         extraKeys: pgBrowser.editor_shortcut_keys,
@@ -127,6 +133,20 @@ define('tools.querytool', [
         matchBrackets: pgAdmin.Browser.editor_options.brace_matching,
       });
 
+      // Updates connection status flag
+      self.gain_focus = function() {
+        setTimeout(function() {
+          SqlEditorUtils.updateConnectionStatusFlag('visible');
+        }, 100);
+      };
+      // Updates connection status flag
+      self.lost_focus = function() {
+        setTimeout(function() {
+          SqlEditorUtils.updateConnectionStatusFlag('hidden');
+        }, 100);
+      };
+
+
       // Create main wcDocker instance
       var main_docker = new wcDocker(
         '#editor-panel', {
@@ -136,7 +156,7 @@ define('tools.querytool', [
             'filename': 'css',
           }),
           theme: 'webcabin.overrides.css',
-        });
+      });
 
       var sql_panel = new pgAdmin.Browser.Panel({
         name: 'sql_panel',
@@ -162,8 +182,12 @@ define('tools.querytool', [
           widget: '\u2026',
         },
         foldGutter: {
-          rangeFinder: 
CodeMirror.fold.combine(CodeMirror.pgadminBeginRangeFinder, 
CodeMirror.pgadminIfRangeFinder,
-            CodeMirror.pgadminLoopRangeFinder, 
CodeMirror.pgadminCaseRangeFinder),
+          rangeFinder: CodeMirror.fold.combine(
+            CodeMirror.pgadminBeginRangeFinder,
+            CodeMirror.pgadminIfRangeFinder,
+            CodeMirror.pgadminLoopRangeFinder,
+            CodeMirror.pgadminCaseRangeFinder
+          ),
         },
         gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
         extraKeys: pgBrowser.editor_shortcut_keys,
@@ -270,14 +294,47 @@ define('tools.querytool', [
 
             // Set focus on query tool of active panel
             p.on(wcDocker.EVENT.GAIN_FOCUS, function() {
-              if (!$(p.$container).hasClass('wcPanelTabContentHidden')) {
-                setTimeout(function() {
-                  self.handler.gridView.query_tool_obj.focus();
-                }, 200);
+              var $container = $(this.$container),
+                transId = self.handler.transId;
+              // If transaction id is already set
+              if($container.data('trans-id') != transId) {
+                $container.data('trans-id', transId)
+              }
+
+              if (!$container.hasClass('wcPanelTabContentHidden')) {
+                 setTimeout(function () {
+                   self.handler.gridView.query_tool_obj.focus();
+                 }, 200);
+                // Trigger an event to update connection status flag
+                pgBrowser.Events.trigger(
+                  'pgadmin:query_tool:panel:gain_focus:' + transId
+                );
               }
             });
-          }
-        });
+
+            // When any query tool panel lost it focus then
+            p.on(wcDocker.EVENT.LOST_FOCUS, function () {
+              var $container = $(this.$container),
+                transId = $container.data('trans-id');
+              // Trigger an event to update connection status flag
+              if ($container.hasClass('wcPanelTabContentHidden')) {
+                pgBrowser.Events.trigger(
+                  'pgadmin:query_tool:panel:lost_focus:' + transId
+                );
+               }
+             });
+           }
+         });
+
+        // Code to update connection status polling flag
+        pgBrowser.Events.on(
+          'pgadmin:query_tool:panel:gain_focus:' + self.handler.transId,
+          self.gain_focus, self
+        );
+        pgBrowser.Events.on(
+          'pgadmin:query_tool:panel:lost_focus:' + self.handler.transId,
+          self.lost_focus, self
+        );
       }
 
       // set focus on query tool once loaded
@@ -422,6 +479,34 @@ define('tools.querytool', [
       });
     },
 
+    // This function will check the connection status at specific
+    // interval defined by the user in preference
+    checkConnectionStatus: function() {
+      var self = this,
+        preference = window.top.pgAdmin.Browser.get_preference(
+          'sqleditor', 'connection_status_fetch_time'
+        ),
+        display_status = window.top.pgAdmin.Browser.get_preference(
+          'sqleditor', 'connection_status'
+        );
+      if(!preference && self.handler.is_new_browser_tab) {
+        preference = window.opener.pgAdmin.Browser.get_preference(
+          'sqleditor', 'connection_status_fetch_time'
+        );
+        display_status = window.opener.pgAdmin.Browser.get_preference(
+          'sqleditor', 'connection_status'
+        );
+      }
+
+      // Only enable pooling if it is enabled
+      if (display_status && display_status.value) {
+        SqlEditorUtils.updateConnectionStatus(
+          url_for('sqleditor.connection_status', {'trans_id': self.transId}),
+          preference.value
+        );
+      }
+    },
+
     /* To prompt user for unsaved changes */
     user_confirmation: function(panel, msg) {
       // If there is anything to save then prompt user
diff --git a/web/webpack.config.js b/web/webpack.config.js
index 726c7f5..b989a4c 100644
--- a/web/webpack.config.js
+++ b/web/webpack.config.js
@@ -268,6 +268,12 @@ module.exports = {
     }, {
       test: /\.(eot|svg|ttf|woff|woff2)$/,
       loader: 'file-loader?name=fonts/[name].[ext]',
+      include: [
+        /node_modules/,
+        path.join(sourceDir, '/css/'),
+        path.join(sourceDir, '/scss/'),
+        path.join(sourceDir, '/fonts/'),
+      ],
       exclude: /vendor/,
     }, {
       test: /\.scss$/,

Reply via email to