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$/,