///////////////////////////////////////////////////////////// // // pgAdmin 4 - PostgreSQL Tools // // Copyright (C) 2013 - 2019, The pgAdmin Development Team // This software is released under the PostgreSQL Licence // ////////////////////////////////////////////////////////////// define('tools.querytool', [ 'babel-polyfill', 'sources/gettext', 'sources/url_for', 'jquery', 'jquery.ui', 'jqueryui.position', 'underscore', 'underscore.string', 'pgadmin.alertifyjs', 'sources/pgadmin', 'backbone', 'sources/../bundle/codemirror', 'pgadmin.misc.explain', 'sources/selection/grid_selector', 'sources/selection/active_cell_capture', 'sources/selection/clipboard', 'sources/selection/copy_data', 'sources/selection/range_selection_helper', 'sources/slickgrid/event_handlers/handle_query_output_keyboard_event', 'sources/selection/xcell_selection_model', 'sources/selection/set_staged_rows', 'sources/sqleditor_utils', 'sources/sqleditor/execute_query', 'sources/sqleditor/query_tool_http_error_handler', 'sources/sqleditor/filter_dialog', 'sources/sqleditor/geometry_viewer', 'sources/sqleditor/history/history_collection.js', 'sources/sqleditor/history/query_history', 'sources/keyboard_shortcuts', 'sources/sqleditor/query_tool_actions', 'sources/sqleditor/query_tool_notifications', 'pgadmin.datagrid', 'sources/modify_animation', 'sources/sqleditor/calculate_query_run_time', 'sources/sqleditor/call_render_after_poll', 'sources/sqleditor/query_tool_preferences', 'sources/csrf', 'sources/../bundle/slickgrid', 'pgadmin.file_manager', 'backgrid.sizeable.columns', 'slick.pgadmin.formatters', 'slick.pgadmin.editors', 'slick.pgadmin.plugins/slick.autocolumnsize', 'pgadmin.browser', 'pgadmin.tools.user_management', ], function( babelPollyfill, gettext, url_for, $, jqueryui, jqueryui_position, _, S, alertify, pgAdmin, Backbone, codemirror, pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, GeometryViewer, historyColl, queryHist, keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid, modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref, csrfToken) { /* Return back, this has been called more than once */ if (pgAdmin.SqlEditor) return pgAdmin.SqlEditor; // Some scripts do export their object in the window only. // Generally the one, which do no have AMD support. var wcDocker = window.wcDocker, pgBrowser = pgAdmin.Browser, CodeMirror = codemirror.default, Slick = window.Slick, HistoryCollection = historyColl.default, QueryHistory = queryHist.default; csrfToken.setPGCSRFToken(pgAdmin.csrf_token_header, pgAdmin.csrf_token); var is_query_running = false; // Defining Backbone view for the sql grid. var SQLEditorView = Backbone.View.extend({ initialize: function(opts) { this.$el = opts.el; this.handler = opts.handler; this.handler['col_size'] = {}; let browser = window.opener ? window.opener.pgAdmin.Browser : window.top.pgAdmin.Browser; this.preferences = browser.get_preferences_for_module('sqleditor'); this.handler.preferences = this.preferences; this.connIntervalId = null; this.layout = opts.layout; }, // Bind all the events events: { 'click .btn-load-file': 'on_file_load', 'click #btn-save': 'on_save', 'click #btn-file-menu-save': 'on_save', 'click #btn-file-menu-save-as': 'on_save_as', 'click #btn-find': 'on_find', 'click #btn-find-menu-find': 'on_find', 'click #btn-find-menu-find-next': 'on_find_next', 'click #btn-find-menu-find-previous': 'on_find_previous', 'click #btn-find-menu-replace': 'on_replace', 'click #btn-find-menu-replace-all': 'on_replace_all', 'click #btn-find-menu-find-persistent': 'on_find_persistent', 'click #btn-find-menu-jump': 'on_jump', 'click #btn-delete-row': 'on_delete', 'click #btn-filter': 'on_show_filter', 'click #btn-filter-menu': 'on_show_filter', 'click #btn-include-filter': 'on_include_filter', 'click #btn-exclude-filter': 'on_exclude_filter', 'click #btn-remove-filter': 'on_remove_filter', 'click #btn-cancel': 'on_cancel', 'click #btn-copy-row': 'on_copy_row', 'click #btn-paste-row': 'on_paste_row', 'click #btn-flash': 'on_flash', 'click #btn-flash-menu': 'on_flash', 'click #btn-cancel-query': 'on_cancel_query', 'click #btn-download': 'on_download', 'click #btn-clear': 'on_clear', 'click #btn-auto-commit': 'on_auto_commit', 'click #btn-auto-rollback': 'on_auto_rollback', 'click #btn-clear-history': 'on_clear_history', 'click .noclose': 'do_not_close_menu', 'click #btn-explain': 'on_explain', 'click #btn-explain-analyze': 'on_explain_analyze', 'click #btn-explain-verbose': 'on_explain_verbose', 'click #btn-explain-costs': 'on_explain_costs', 'click #btn-explain-buffers': 'on_explain_buffers', 'click #btn-explain-timing': 'on_explain_timing', 'change .limit': 'on_limit_change', 'keydown': 'keyAction', // Comment options 'click #btn-comment-code': 'on_toggle_comment_block_code', 'click #btn-toggle-comment-block': 'on_toggle_comment_block_code', 'click #btn-comment-line': 'on_comment_line_code', 'click #btn-uncomment-line': 'on_uncomment_line_code', // Indentation options 'click #btn-indent-code': 'on_indent_code', 'click #btn-unindent-code': 'on_unindent_code', 'click #btn-commit': 'on_commit_transaction', 'click #btn-rollback': 'on_rollback_transaction', }, reflectPreferences: function() { let self = this, browser = window.opener ? window.opener.pgAdmin.Browser : window.top.pgAdmin.Browser; /* pgBrowser is different obj from window.top.pgAdmin.Browser * Make sure to get only the latest update. Older versions will be discarded * if function is called by older events. * This works for new tab sql editor also as it polls if latest version available * This is required because sql editor can update preferences directly */ if(pgBrowser.preference_version() < browser.preference_version()){ pgBrowser.preference_version(browser.preference_version()); self.preferences = browser.get_preferences_for_module('sqleditor'); self.handler.preferences = self.preferences; queryToolPref.updateUIPreferences(self); } }, buildDefaultLayout: function(docker) { let sql_panel_obj = docker.addPanel('sql_panel', wcDocker.DOCK.TOP); docker.addPanel('scratch', wcDocker.DOCK.RIGHT, sql_panel_obj); docker.addPanel('history', wcDocker.DOCK.STACKED, sql_panel_obj); let data_output_panel = docker.addPanel('data_output', wcDocker.DOCK.BOTTOM); docker.addPanel('explain', wcDocker.DOCK.STACKED, data_output_panel); docker.addPanel('messages', wcDocker.DOCK.STACKED, data_output_panel); docker.addPanel('notifications', wcDocker.DOCK.STACKED, data_output_panel); }, // This function is used to render the template. render: function() { var self = this; $('.editor-title').text(_.unescape(self.editor_title)); // 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 self.docker = new wcDocker( '#editor-panel', { allowContextMenu: true, allowCollapse: false, loadingClass: 'pg-sp-icon', themePath: url_for('static', { 'filename': 'css', }), theme: 'webcabin.overrides.css', }); // Create the panels var sql_panel = new pgAdmin.Browser.Panel({ name: 'sql_panel', title: gettext('Query Editor'), width: '75%', height: '33%', isCloseable: false, isPrivate: true, }); var data_output = new pgAdmin.Browser.Panel({ name: 'data_output', title: gettext('Data Output'), width: '100%', height: '100%', isCloseable: false, isPrivate: true, extraClasses: 'hide-vertical-scrollbar', content: '
', }); var explain = new pgAdmin.Browser.Panel({ name: 'explain', title: gettext('Explain'), width: '100%', height: '100%', isCloseable: false, isPrivate: true, content: '', }); var messages = new pgAdmin.Browser.Panel({ name: 'messages', title: gettext('Messages'), width: '100%', height: '100%', isCloseable: false, isPrivate: true, content: '', }); var history = new pgAdmin.Browser.Panel({ name: 'history', title: gettext('Query History'), width: '100%', height: '33%', isCloseable: false, isPrivate: true, content: '', }); var scratch = new pgAdmin.Browser.Panel({ name: 'scratch', title: gettext('Scratch Pad'), width: '25%', height: '33%', isCloseable: true, isPrivate: false, content: '', }); var notifications = new pgAdmin.Browser.Panel({ name: 'notifications', title: gettext('Notifications'), width: '100%', height: '100%', isCloseable: false, isPrivate: true, content: '', }); var geometry_viewer = new pgAdmin.Browser.Panel({ name: 'geometry_viewer', title: gettext('Geometry Viewer'), width: '100%', height: '100%', isCloseable: true, isPrivate: true, content: '', }); // Load all the created panels sql_panel.load(self.docker); data_output.load(self.docker); explain.load(self.docker); messages.load(self.docker); history.load(self.docker); scratch.load(self.docker); notifications.load(self.docker); geometry_viewer.load(self.docker); // restore the layout if present else fallback to buildDefaultLayout pgBrowser.restore_layout(self.docker, this.layout, this.buildDefaultLayout.bind(this)); self.docker.on(wcDocker.EVENT.LAYOUT_CHANGED, function() { pgBrowser.save_current_layout('SQLEditor/Layout', self.docker); }); self.sql_panel_obj = self.docker.findPanels('sql_panel')[0]; self.history_panel = self.docker.findPanels('history')[0]; self.data_output_panel = self.docker.findPanels('data_output')[0]; self.explain_panel = self.docker.findPanels('explain')[0]; self.messages_panel = self.docker.findPanels('messages')[0]; self.notifications_panel = self.docker.findPanels('notifications')[0]; // Refresh Code mirror on SQL panel resize to // display its value properly self.sql_panel_obj.on(wcDocker.EVENT.RESIZE_ENDED, function() { setTimeout(function() { if (self && self.query_tool_obj) { self.query_tool_obj.refresh(); } }, 200); }); self.render_history_grid(); queryToolNotifications.renderNotificationsGrid(self.notifications_panel); var text_container = $(''); var output_container = $('').append(text_container); self.sql_panel_obj.$container.find('.pg-panel-content').append(output_container); self.query_tool_obj = CodeMirror.fromTextArea(text_container.get(0), { tabindex: '0', lineNumbers: true, styleSelectedText: true, mode: self.handler.server_type === 'gpdb' ? 'text/x-gpsql' : 'text/x-pgsql', foldOptions: { widget: '\u2026', }, foldGutter: { rangeFinder: CodeMirror.fold.combine( CodeMirror.pgadminBeginRangeFinder, CodeMirror.pgadminIfRangeFinder, CodeMirror.pgadminLoopRangeFinder, CodeMirror.pgadminCaseRangeFinder ), }, gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], extraKeys: pgBrowser.editor_shortcut_keys, scrollbarStyle: 'simple', }); if (!self.preferences.new_browser_tab) { // Listen on the panel closed event and notify user to save modifications. _.each(window.top.pgAdmin.Browser.docker.findPanels('frm_datagrid'), function(p) { if (p.isVisible()) { p.on(wcDocker.EVENT.CLOSING, function() { // Only if we can edit data then perform this check var notify = false, msg; if (self.handler.can_edit && self.preferences.prompt_save_data_changes) { var data_store = self.handler.data_store; if (data_store && (_.size(data_store.added) || _.size(data_store.updated))) { msg = gettext('The data has changed. Do you want to save changes?'); notify = true; } } else if (self.handler.is_query_tool && self.handler.is_query_changed && self.preferences.prompt_save_query_changes) { msg = gettext('The text has changed. Do you want to save changes?'); notify = true; } if (notify) { return self.user_confirmation(p, msg); } return true; }); // Set focus on query tool of active panel p.on(wcDocker.EVENT.GAIN_FOCUS, function() { var $container = $(this.$container), transId = self.handler.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 = self.handler.transId; // 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 setTimeout(function() { self.query_tool_obj.focus(); }, 500); /* We have override/register the hint function of CodeMirror * to provide our own hint logic. */ CodeMirror.registerHelper('hint', 'sql', function(editor) { var data = [], doc = editor.getDoc(), cur = doc.getCursor(), // Get the current cursor position current_cur = cur.ch, // function context ctx = { editor: editor, // URL for auto-complete url: url_for('sqleditor.autocomplete', { 'trans_id': self.transId, }), data: data, // Get the line number in the cursor position current_line: cur.line, /* * Render function for hint to add our own class * and icon as per the object type. */ hint_render: function(elt, data, cur) { var el = document.createElement('span'); switch (cur.type) { case 'database': el.className = 'sqleditor-hint pg-icon-' + cur.type; break; case 'datatype': el.className = 'sqleditor-hint icon-type'; break; case 'keyword': el.className = 'fa fa-key'; break; case 'table alias': el.className = 'fa fa-at'; break; default: el.className = 'sqleditor-hint icon-' + cur.type; } el.appendChild(document.createTextNode(cur.text)); elt.appendChild(el); }, }; data.push(doc.getValue()); // Get the text from start to the current cursor position. data.push( doc.getRange({ line: 0, ch: 0, }, { line: ctx.current_line, ch: current_cur, }) ); return { then: function(cb) { var self = this; // Make ajax call to find the autocomplete data $.ajax({ url: self.url, method: 'POST', contentType: 'application/json', data: JSON.stringify(self.data), }) .done(function(res) { var result = []; _.each(res.data.result, function(obj, key) { result.push({ text: key, type: obj.object_type, render: self.hint_render, }); }); // Sort function to sort the suggestion's alphabetically. result.sort(function(a, b) { var textA = a.text.toLowerCase(), textB = b.text.toLowerCase(); if (textA < textB) //sort string ascending return -1; if (textA > textB) return 1; return 0; //default return value (no sorting) }); /* * Below logic find the start and end point * to replace the selected auto complete suggestion. */ var token = self.editor.getTokenAt(cur), start, end, search; if (token.end > cur.ch) { token.end = cur.ch; token.string = token.string.slice(0, cur.ch - token.start); } if (token.string.match(/^[.`\w@]\w*$/)) { search = token.string; start = token.start; end = token.end; } else { start = end = cur.ch; search = ''; } /* * Added 1 in the start position if search string * started with "." or "`" else auto complete of code mirror * will remove the "." when user select any suggestion. */ if (search.charAt(0) == '.' || search.charAt(0) == '``') start += 1; cb({ list: result, from: { line: self.current_line, ch: start, }, to: { line: self.current_line, ch: end, }, }); }) .fail(function(e) { return httpErrorHandler.handleLoginRequiredAndTransactionRequired( pgAdmin, self, e, null, [], false ); }); }.bind(ctx), }; }); pgBrowser.bind_beforeunload(); /* If the screen width is small and we hover over the Explain Options, * the submenu goes behind the screen on the right side. * Below logic will make it appear on the left. */ $('.dropdown-submenu').on('mouseenter',function() { var menu = $(this).find('ul.dropdown-menu'); var menupos = $(menu).offset(); if (menupos.left + menu.width() > $(window).width()) { var newpos = -$(menu).width(); menu.css('left',newpos); } }).on('mouseleave', function() { var menu = $(this).find('ul.dropdown-menu'); menu.css('left',''); }); self.reflectPreferences(); /* Set Auto-commit and auto-rollback on query editor */ if (self.preferences.auto_commit) { $('.auto-commit').removeClass('visibility-hidden'); } else { $('.auto-commit').addClass('visibility-hidden'); } if (self.preferences.auto_rollback) { $('.auto-rollback').removeClass('visibility-hidden'); } else { $('.auto-rollback').addClass('visibility-hidden'); } /* Register for preference changed event broadcasted in parent * to reload the shorcuts. As sqleditor is in iFrame of wcDocker * window parent is referred */ pgBrowser.onPreferencesChange('sqleditor', function() { self.reflectPreferences(); }); /* If sql editor is in a new tab, event fired is not available * instead, a poller is set up who will check */ if(self.preferences.new_browser_tab) { setInterval(()=>{ if(window.opener.pgAdmin) { self.reflectPreferences(); } }, 1000); } }, /* To prompt user for unsaved changes */ user_confirmation: function(panel, msg) { // If there is anything to save then prompt user var that = this; alertify.confirmSave || alertify.dialog('confirmSave', function() { return { main: function(title, message) { this.setHeader(title); this.setContent(message); }, setup: function() { return { buttons: [{ text: gettext('Cancel'), key: 27, // ESC invokeOnClose: true, className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button', }, { text: gettext('Don\'t save'), className: 'btn btn-secondary fa fa-lg fa-trash-o pg-alertify-button', }, { text: gettext('Save'), className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button', }], focus: { element: 0, select: false, }, options: { maximizable: false, resizable: false, }, }; }, callback: function(closeEvent) { switch (closeEvent.index) { case 0: // Cancel //Do nothing. break; case 1: // Don't Save that.handler.close_on_save = false; that.handler.close(); break; case 2: //Save that.handler.close_on_save = true; that.handler._save(that, that.handler); break; } }, }; }); alertify.confirmSave(gettext('Save changes?'), msg); return false; }, /* Regarding SlickGrid usage in render_grid function. SlickGrid Plugins: ------------------ 1) Slick.AutoTooltips - This plugin is useful for displaying cell data as tooltip when user hover mouse on cell if data is large 2) Slick.CheckboxSelectColumn - This plugin is useful for selecting rows using checkbox 3) RowSelectionModel - This plugin is needed by CheckboxSelectColumn plugin to select rows 4) Slick.HeaderButtons - This plugin is useful for add buttons in column header Grid Options: ------------- 1) editable - This option allow us to make grid editable 2) enableAddRow - This option allow us to add new rows at the end of grid 3) enableCellNavigation - This option allow us to navigate cells using keyboard 4) enableColumnReorder - This option allow us to record column 5) asyncEditorLoading - This option allow us to open editor async 6) autoEdit - This option allow us to enter in edit mode directly when user clicks on it otherwise user have to double click or manually press enter on cell to go in cell edit mode Handling of data: ----------------- We are doing data handling manually,what user adds/updates/deletes etc we will use `data_store` object to store everything user does within grid data - updated: This will hold all the data which user updates in grid - added: This will hold all the new row(s) data which user adds in grid - staged_rows: This will hold all the data which user copies/pastes/deletes in grid - deleted: This will hold all the data which user deletes in grid Events handling: ---------------- 1) onCellChange - We are using this event to listen to changes on individual cell. 2) onAddNewRow - We are using this event to listen to new row adding functionality. 3) onSelectedRangesChanged - We are using this event to listen when user selects rows for copy/delete operation. 4) onBeforeEditCell - We are using this event to save the data before users modified them 5) onKeyDown - We are using this event for Copy operation on grid */ // This function is responsible to create and render the SlickGrid. render_grid: function(collection, columns, is_editable, client_primary_key, rows_affected) { var self = this; // This will work as data store and holds all the // inserted/updated/deleted data from grid self.handler.data_store = { updated: {}, added: {}, staged_rows: {}, deleted: {}, updated_index: {}, added_index: {}, }; // To store primary keys before they gets changed self.handler.primary_keys_data = {}; self.client_primary_key = client_primary_key; self.client_primary_key_counter = 0; // Remove any existing grid first if (self.handler.slickgrid) { self.handler.slickgrid.destroy(); } if (!_.isArray(collection) || !_.size(collection)) { collection = []; } var grid_columns = [], table_name; var column_size = self.handler['col_size'], query = self.handler.query, // Extract table name from query table_list = query.match(/select.*from\s+\w+\.*(\w+)/i); if (!table_list) { table_name = SqlEditorUtils.getHash(query); } else { table_name = table_list[1]; } self.handler['table_name'] = table_name; column_size[table_name] = column_size[table_name] || {}; _.each(columns, function(c) { var options = { id: c.name, pos: c.pos, field: c.name, name: c.label, display_name: c.display_name, column_type: c.column_type, column_type_internal: c.column_type_internal, not_null: c.not_null, has_default_val: c.has_default_val, is_array: c.is_array, }; // Get the columns width based on longer string among data type or // column name. var column_type = c.column_type.trim(); var label = c.name.length > column_type.length ? c.name : column_type; if (_.isUndefined(column_size[table_name][c.name])) { options['width'] = SqlEditorUtils.calculateColumnWidth(label); column_size[table_name][c.name] = options['width']; } else { options['width'] = column_size[table_name][c.name]; } // If grid is editable then add editor else make it readonly if (c.cell == 'oid' && c.name == 'oid') { options['editor'] = null; } else if (c.cell == 'Json') { options['editor'] = is_editable ? Slick.Editors.JsonText : Slick.Editors.ReadOnlyJsonText; options['formatter'] = Slick.Formatters.JsonString; } else if (c.cell == 'number' || c.cell == 'oid' || $.inArray(c.type, ['xid', 'real']) !== -1 ) { options['editor'] = is_editable ? Slick.Editors.CustomNumber : Slick.Editors.ReadOnlyText; options['formatter'] = Slick.Formatters.Numbers; } else if (c.cell == 'boolean') { options['editor'] = is_editable ? Slick.Editors.Checkbox : Slick.Editors.ReadOnlyCheckbox; options['formatter'] = Slick.Formatters.Checkmark; } else if (c.cell == 'binary') { // We do not support editing binary data in SQL editor and data grid. options['formatter'] = Slick.Formatters.Binary; } else if (c.cell == 'geometry' || c.cell == 'geography') { // increase width to add 'view' button options['width'] += 28; } else { options['editor'] = is_editable ? Slick.Editors.pgText : Slick.Editors.ReadOnlypgText; options['formatter'] = Slick.Formatters.Text; } grid_columns.push(options); }); var gridSelector = new GridSelector(); grid_columns = self.grid_columns = gridSelector.getColumnDefinitions(grid_columns); // add 'view' button in geometry and geography type column header _.each(grid_columns, function (c) { if (c.column_type_internal == 'geometry' || c.column_type_internal == 'geography') { GeometryViewer.add_header_button(c); } }); if (rows_affected) { // calculate with for header row column. grid_columns[0]['width'] = SqlEditorUtils.calculateColumnWidth(rows_affected); } var grid_options = { editable: true, enableAddRow: is_editable, enableCellNavigation: true, enableColumnReorder: false, asyncEditorLoading: false, autoEdit: false, }; var $data_grid = $('#datagrid'); // Calculate height based on panel size at runtime & set it var grid_height = $(this.data_output_panel.$container.parent().parent()).height() - 35; $data_grid.height(grid_height); var dataView = self.dataView = new Slick.Data.DataView(), grid = self.grid = new Slick.Grid($data_grid, dataView, grid_columns, grid_options); // Add-on function which allow us to identify the faulty row after insert/update // and apply css accordingly dataView.getItemMetadata = function(i) { var cssClass = '', data_store = self.handler.data_store; if (_.has(self.handler, 'data_store')) { if (i in data_store.added_index && data_store.added_index[i] in data_store.added) { cssClass = 'new_row'; if (data_store.added[data_store.added_index[i]].err) { cssClass += ' error'; } } else if (i in data_store.updated_index && i in data_store.updated) { cssClass = 'updated_row'; if (data_store.updated[data_store.updated_index[i]].err) { cssClass += ' error'; } } } // Disable rows having default values if (!_.isUndefined(self.handler.rows_to_disable) && self.handler.rows_to_disable.length > 0 && _.indexOf(self.handler.rows_to_disable, i) !== -1) { cssClass += ' disabled_row'; } return { 'cssClasses': cssClass, }; }; grid.registerPlugin(new Slick.AutoTooltips({ enableForHeaderCells: false, })); grid.registerPlugin(new ActiveCellCapture()); grid.setSelectionModel(new XCellSelectionModel()); grid.registerPlugin(gridSelector); grid.registerPlugin(new Slick.AutoColumnSize()); var headerButtonsPlugin = new Slick.Plugins.HeaderButtons(); headerButtonsPlugin.onCommand.subscribe(function (e, args) { let command = args.command; if (command === 'view-geometries') { let columns = args.grid.getColumns(); let columnIndex = columns.indexOf(args.column); let selectedRows = args.grid.getSelectedRows(); if (selectedRows.length === 0) { // if no rows are selected, load and render all the rows if (self.handler.has_more_rows) { self.fetch_next_all(function () { // trigger onGridSelectAll manually with new event data. gridSelector.onGridSelectAll.notify(args, new Slick.EventData()); let items = args.grid.getData().getItems(); GeometryViewer.render_geometries(self.handler, items, columns, columnIndex); }); } else { gridSelector.onGridSelectAll.notify(args, new Slick.EventData()); let items = args.grid.getData().getItems(); GeometryViewer.render_geometries(self.handler, items, columns, columnIndex); } } else { // render selected rows let items = args.grid.getData().getItems(); let selectedItems = _.map(selectedRows, function (row) { return items[row]; }); GeometryViewer.render_geometries(self.handler, selectedItems, columns, columnIndex); } } }); grid.registerPlugin(headerButtonsPlugin); var editor_data = { keys: (_.isEmpty(self.handler.primary_keys) && self.handler.has_oids) ? self.handler.oids : self.handler.primary_keys, vals: collection, columns: columns, grid: grid, selection: grid.getSelectionModel(), editor: self, client_primary_key: self.client_primary_key, has_oids: self.handler.has_oids, }; self.handler.slickgrid = grid; self.handler.slickgrid.CSVOptions = { quoting: self.preferences.results_grid_quoting, quote_char: self.preferences.results_grid_quote_char, field_separator: self.preferences.results_grid_field_separator, }; // Listener function to watch selected rows from grid if (editor_data.selection) { editor_data.selection.onSelectedRangesChanged.subscribe( setStagedRows.bind(editor_data)); } grid.onColumnsResized.subscribe(function() { var columns = this.getColumns(); _.each(columns, function(col) { var column_size = self.handler['col_size']; column_size[self.handler['table_name']][col['id']] = col['width']; }); }.bind(grid)); gridSelector.onBeforeGridSelectAll.subscribe(function(e, args) { if (self.handler.has_more_rows) { // this will prevent selection un-till we load all data e.stopImmediatePropagation(); self.fetch_next_all(function() { // since we've stopped event propagation we need to // trigger onGridSelectAll manually with new event data. gridSelector.onGridSelectAll.notify(args, new Slick.EventData()); }); } }); gridSelector.onBeforeGridColumnSelectAll.subscribe(function(e, args) { if (self.handler.has_more_rows) { // this will prevent selection un-till we load all data e.stopImmediatePropagation(); self.fetch_next_all(function() { // since we've stopped event propagation we need to // trigger onGridColumnSelectAll manually with new event data. gridSelector.onGridColumnSelectAll.notify(args, new Slick.EventData()); }); } }); // listen for row count change. dataView.onRowCountChanged.subscribe(function() { grid.updateRowCount(); grid.render(); }); // listen for rows change. dataView.onRowsChanged.subscribe(function(e, args) { grid.invalidateRows(args.rows); grid.render(); }); // Listener function which will be called before user updates existing cell // This will be used to collect primary key for that row grid.onBeforeEditCell.subscribe(function(e, args) { if (args.column.column_type_internal == 'bytea' || args.column.column_type_internal == 'bytea[]') { return false; } var before_data = args.item; // If newly added row is saved but grid is not refreshed, // then disable cell editing for that row if (self.handler.rows_to_disable && _.contains(self.handler.rows_to_disable, args.row)) { return false; } if (self.handler.can_edit && before_data && self.client_primary_key in before_data) { var _pk = before_data[self.client_primary_key], _keys = self.handler.primary_keys, current_pk = {}; // If already have primary key data then no need to go ahead if (_pk in self.handler.primary_keys_data) { return; } // Fetch primary keys for the row before they gets modified _.each(_keys, function(value, key) { current_pk[key] = before_data[key]; }); // Place it in main variable for later use self.handler.primary_keys_data[_pk] = current_pk; } }); grid.onKeyDown.subscribe(function(event, args) { var KEY_A = 65; var modifiedKey = event.keyCode; var isModifierDown = event.ctrlKey || event.metaKey; // Intercept Ctrl/Cmd + A key board event. // As we might want to load all rows before selecting all. if (isModifierDown && modifiedKey == KEY_A && self.handler.has_more_rows) { self.fetch_next_all(function() { handleQueryOutputKeyboardEvent(event, args); }); } else { handleQueryOutputKeyboardEvent(event, args); } }); // Listener function which will be called when user updates existing rows grid.onCellChange.subscribe(function(e, args) { // self.handler.data_store.updated will holds all the updated data var changed_column = args.grid.getColumns()[args.cell].field, updated_data = args.item[changed_column], // New value for current field _pk = args.item[self.client_primary_key] || null, // Unique key to identify row column_data = {}; // Access to row/cell value after a cell is changed. // The purpose is to remove row_id from temp_new_row // if new row has primary key instead of [default_value] // so that cell edit is enabled for that row. var grid = args.grid, row_data = grid.getDataItem(args.row), is_primary_key = self.primary_keys && _.all( _.values( _.pick( row_data, self.primary_keys ) ), function(val) { return val != undefined; } ); // temp_new_rows is available only for view data. if (is_primary_key && self.handler.temp_new_rows) { var index = self.handler.temp_new_rows.indexOf(args.row); if (index > -1) { self.handler.temp_new_rows.splice(index, 1); } } column_data[changed_column] = updated_data; if (_pk) { // Check if it is in newly added row by user? if (_pk in self.handler.data_store.added) { _.extend( self.handler.data_store.added[_pk]['data'], column_data); //Find type for current column self.handler.data_store.added[_pk]['err'] = false; // Check if it is updated data from existing rows? } else if (_pk in self.handler.data_store.updated) { _.extend( self.handler.data_store.updated[_pk]['data'], column_data ); self.handler.data_store.updated[_pk]['err'] = false; } else { // First updated data for this primary key self.handler.data_store.updated[_pk] = { 'err': false, 'data': column_data, 'primary_keys': self.handler.primary_keys_data[_pk], }; self.handler.data_store.updated_index[args.row] = _pk; } } // Enable save button $('#btn-save').prop('disabled', false); }.bind(editor_data)); // Listener function which will be called when user adds new rows grid.onAddNewRow.subscribe(function(e, args) { // self.handler.data_store.added will holds all the newly added rows/data var column = args.column, item = args.item, data_length = this.grid.getDataLength(), _key = (self.client_primary_key_counter++).toString(), dataView = this.grid.getData(); // Add new row in list to keep track of it if (_.isUndefined(item[0])) { self.handler.temp_new_rows.push(data_length); } // If copied item has already primary key, use it. if (item) { item[self.client_primary_key] = _key; } dataView.addItem(item); self.handler.data_store.added[_key] = { 'err': false, 'data': item, }; self.handler.data_store.added_index[data_length] = _key; // Fetch data type & add it for the column var temp = {}; temp[column.name] = _.where(this.columns, { pos: column.pos, })[0]['type']; grid.updateRowCount(); grid.render(); // Enable save button $('#btn-save').prop('disabled', false); }.bind(editor_data)); // Listen grid viewportChanged event to load next chunk of data. grid.onViewportChanged.subscribe(function(e, args) { var rendered_range = args.grid.getRenderedRange(), data_len = args.grid.getDataLength(); // start fetching next batch of records before reaching to bottom. if (self.handler.has_more_rows && !self.handler.fetching_rows && rendered_range.bottom > data_len - 100) { // fetch asynchronous setTimeout(self.fetch_next.bind(self)); } }); // Resize SlickGrid when window resize $(window).resize(function() { // Resize grid only when 'Data Output' panel is visible. if (self.data_output_panel.isVisible()) { self.grid_resize(grid); } }); // Resize SlickGrid when output Panel resize self.data_output_panel.on(wcDocker.EVENT.RESIZE_ENDED, function() { // Resize grid only when 'Data Output' panel is visible. if (self.data_output_panel.isVisible()) { self.grid_resize(grid); } }); // Resize SlickGrid when output Panel gets focus self.data_output_panel.on(wcDocker.EVENT.VISIBILITY_CHANGED, function() { // Resize grid only if output panel is visible if (self.data_output_panel.isVisible()) self.grid_resize(grid); }); for (var i = 0; i < collection.length; i++) { // Convert to dict from 2darray var item = {}; for (var j = 1; j < grid_columns.length; j++) { item[grid_columns[j]['field']] = collection[i][grid_columns[j]['pos']]; } item[self.client_primary_key] = (self.client_primary_key_counter++).toString(); collection[i] = item; } dataView.setItems(collection, self.client_primary_key); }, fetch_next_all: function(cb) { this.fetch_next(true, cb); }, fetch_next: function(fetch_all, cb) { var self = this, url = ''; // This will prevent fetch operation if previous fetch operation is // already in progress. self.handler.fetching_rows = true; $('#btn-flash').prop('disabled', true); $('#btn-download').prop('disabled', true); if (fetch_all) { self.handler.trigger( 'pgadmin-sqleditor:loading-icon:show', gettext('Fetching all records...') ); url = url_for('sqleditor.fetch_all', { 'trans_id': self.transId, 'fetch_all': 1, }); } else { url = url_for('sqleditor.fetch', { 'trans_id': self.transId, }); } $.ajax({ url: url, method: 'GET', }) .done(function(res) { self.handler.has_more_rows = res.data.has_more_rows; $('#btn-flash').prop('disabled', false); $('#btn-download').prop('disabled', false); self.handler.trigger('pgadmin-sqleditor:loading-icon:hide'); self.update_grid_data(res.data.result); self.handler.fetching_rows = false; if (typeof cb == 'function') { cb(); } }) .fail(function(e) { $('#btn-flash').prop('disabled', false); $('#btn-download').prop('disabled', false); self.handler.trigger('pgadmin-sqleditor:loading-icon:hide'); self.handler.has_more_rows = false; self.handler.fetching_rows = false; if (typeof cb == 'function') { cb(); } let msg = httpErrorHandler.handleQueryToolAjaxError( pgAdmin, self, e, null, [], false ); self.update_msg_history(false, msg); }); }, update_grid_data: function(data) { this.dataView.beginUpdate(); for (var i = 0; i < data.length; i++) { // Convert 2darray to dict. var item = {}; for (var j = 1; j < this.grid_columns.length; j++) { item[this.grid_columns[j]['field']] = data[i][this.grid_columns[j]['pos']]; } item[this.client_primary_key] = (this.client_primary_key_counter++).toString(); this.dataView.addItem(item); } this.dataView.endUpdate(); }, /* This function is responsible to render output grid */ grid_resize: function(grid) { var prev_height = $('#datagrid').height(), h = $(this.data_output_panel.$container.parent().parent()).height() - 35, prev_viewport = grid.getViewport(), prev_viewport_rows = grid.getRenderedRange(), prev_cell = grid.getActiveCell(); // Apply css only if necessary, To avoid DOM operation if (prev_height != h) { $('#datagrid').css({ 'height': h + 'px', }); } grid.resizeCanvas(); /* * If there is an active cell from user then we have to go to that cell */ if (prev_cell) { grid.scrollCellIntoView(prev_cell.row, prev_cell.cell); } // If already displaying from first row if (prev_viewport.top == prev_viewport_rows.top) { return; } // if user has scroll to the end/last row else if (prev_viewport.bottom - 2 == prev_viewport_rows.bottom) { grid.scrollRowIntoView(prev_viewport.bottom); } else { grid.scrollRowIntoView(prev_viewport.bottom - 2); } }, /* This function is responsible to create and render the * new backgrid for the history tab. */ render_history_grid: function() { var self = this; /* Should not reset if function called again */ if(!self.history_collection) { self.history_collection = new HistoryCollection([]); } if(!self.historyComponent) { self.historyComponent = new QueryHistory($('#history_grid'), self.history_collection); /* Copy query to query editor, set the focus to editor and move cursor to end */ self.historyComponent.onCopyToEditorClick((query)=>{ self.query_tool_obj.setValue(query); self.sql_panel_obj.focus(); setTimeout(() => { self.query_tool_obj.focus(); self.query_tool_obj.setCursor(self.query_tool_obj.lineCount(), 0); }, 100); }); self.historyComponent.render(); self.history_panel.off(wcDocker.EVENT.VISIBILITY_CHANGED); self.history_panel.on(wcDocker.EVENT.VISIBILITY_CHANGED, function() { if (self.history_panel.isVisible()) { setTimeout(()=>{ self.historyComponent.focus(); }, 100); } }); } // Make ajax call to get history data except view/edit data if(self.handler.is_query_tool) { $.ajax({ url: url_for('sqleditor.get_query_history', { 'trans_id': self.handler.transId, }), method: 'GET', contentType: 'application/json', }) .done(function(res) { res.data.result.map((entry) => { let newEntry = JSON.parse(entry); newEntry.start_time = new Date(newEntry.start_time); self.history_collection.add(newEntry); }); }) .fail(function() { /* history fetch fail should not affect query tool */ }); } else { self.historyComponent.setEditorPref({'copy_to_editor':false}); } }, // Callback function for delete button click. on_delete: function() { var self = this; // Trigger the deleterow signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:deleterow', self, self.handler ); }, _stopEventPropogation: function(ev) { ev = ev || window.event; ev.cancelBubble = true; ev.stopPropagation(); ev.stopImmediatePropagation(); ev.preventDefault(); }, _closeDropDown: function(ev) { var target = ev && (ev.currentTarget || ev.target); if (target) { $(target).closest('.editor-toolbar').find('.show').removeClass('show'); } }, // Callback function for Save button click. on_save: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); self.handler.close_on_save = false; // Trigger the save signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:save', self, self.handler ); }, // Callback function for Save button click. on_save_as: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); self.handler.close_on_save = false; // Trigger the save signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:save', self, self.handler, true ); }, // Callback function for the find button click. on_find: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); self.query_tool_obj.execCommand('find'); }, // Callback function for the find next button click. on_find_next: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); self.query_tool_obj.execCommand('findNext'); }, // Callback function for the find previous button click. on_find_previous: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); self.query_tool_obj.execCommand('findPrev'); }, // Callback function for the replace button click. on_replace: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); self.query_tool_obj.execCommand('replace'); }, // Callback function for the replace all button click. on_replace_all: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); self.query_tool_obj.execCommand('replaceAll'); }, // Callback function for the find persistent button click. on_find_persistent: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); self.query_tool_obj.execCommand('findPersistent'); }, // Callback function for the jump button click. on_jump: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); self.query_tool_obj.execCommand('jumpToLine'); }, // Callback function for filter button click. on_show_filter: function() { var self = this; // Trigger the show_filter signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:show_filter', self, self.handler ); }, // Callback function for include filter button click. on_include_filter: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); // Trigger the include_filter signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:include_filter', self, self.handler ); }, // Callback function for exclude filter button click. on_exclude_filter: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); // Trigger the exclude_filter signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:exclude_filter', self, self.handler ); }, // Callback function for remove filter button click. on_remove_filter: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); // Trigger the remove_filter signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:remove_filter', self, self.handler ); }, // Callback function for cancel button click. on_cancel: function() { $('#filter').addClass('d-none'); $('#editor-panel').removeClass('sql-editor-busy-fetching'); }, // Callback function for copy button click. on_copy_row: function() { var self = this; // Trigger the copy signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:copy_row', self, self.handler ); }, // Callback function for paste button click. on_paste_row: function() { var self = this; // Trigger the paste signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:paste_row', self, self.handler ); }, // Callback function for the change event of combo box on_limit_change: function() { var self = this; // Trigger the limit signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:limit', self, self.handler ); }, // Callback function for the flash button click. on_flash: function() { queryToolActions.executeQuery(this.handler); }, // Callback function for the cancel query button click. on_cancel_query: function() { var self = this; // Trigger the cancel-query signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:cancel-query', self, self.handler ); }, // Callback function for the line comment code on_comment_line_code: function() { queryToolActions.commentLineCode(this.handler); }, // Callback function for the line uncomment code on_uncomment_line_code: function() { queryToolActions.uncommentLineCode(this.handler); }, // Callback function for the block comment/uncomment code on_toggle_comment_block_code: function() { queryToolActions.commentBlockCode(this.handler); }, on_indent_code: function() { var self = this; // Trigger the comment signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:indent_selected_code', self, self.handler ); }, on_unindent_code: function() { var self = this; // Trigger the comment signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:unindent_selected_code', self, self.handler ); }, // Callback function for the clear button click. on_clear: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); /* If is_query_changed flag is set to false then no need to * confirm with the user for unsaved changes. */ if (self.handler.is_query_changed) { alertify.confirm( gettext('Unsaved changes'), gettext('Are you sure you wish to discard the current changes?'), function() { // Do nothing as user do not want to save, just continue self.query_tool_obj.setValue(''); setTimeout(() => { self.query_tool_obj.focus(); }, 200); }, function() { return true; } ).set('labels', { ok: gettext('Yes'), cancel: gettext('No'), }); } else { self.query_tool_obj.setValue(''); } }, // Callback function for the clear history button click. on_clear_history: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); // ask for confirmation only if anything to clear if (!self.history_collection.length()) { return; } alertify.confirm(gettext('Clear history'), gettext('Are you sure you wish to clear the history?') + '' + gettext('This will remove all of your query history from this and other sessions for this database.'), function() { if (self.history_collection) { self.history_collection.reset(); } if(self.handler.is_query_tool) { $.ajax({ url: url_for('sqleditor.clear_query_history', { 'trans_id': self.handler.transId, }), method: 'DELETE', contentType: 'application/json', }) .done(function() {}) .fail(function() { /* history clear fail should not affect query tool */ }); } setTimeout(() => { self.query_tool_obj.focus(); }, 200); }, function() { return true; } ).set('labels', { ok: gettext('Yes'), cancel: gettext('No'), }); }, // Callback function for the auto commit button click. on_auto_commit: function(ev) { var self = this; this._stopEventPropogation(ev); // Trigger the auto-commit signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:auto_commit', self, self.handler ); }, // Callback function for the auto rollback button click. on_auto_rollback: function(ev) { var self = this; this._stopEventPropogation(ev); // Trigger the download signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:auto_rollback', self, self.handler ); }, // Callback function for explain button click. on_explain: function(event) { this._stopEventPropogation(event); this._closeDropDown(event); queryToolActions.explain(this.handler); }, // Callback function for explain analyze button click. on_explain_analyze: function(event) { this._stopEventPropogation(event); this._closeDropDown(event); queryToolActions.explainAnalyze(this.handler); }, // Callback function for explain option "verbose" button click on_explain_verbose: function(ev) { var self = this; this._stopEventPropogation(ev); // Trigger the explain "verbose" signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:explain-verbose', self, self.handler ); }, // Callback function for explain option "costs" button click on_explain_costs: function(ev) { var self = this; this._stopEventPropogation(ev); // Trigger the explain "costs" signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:explain-costs', self, self.handler ); }, // Callback function for explain option "buffers" button click on_explain_buffers: function(ev) { var self = this; this._stopEventPropogation(ev); // Trigger the explain "buffers" signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:explain-buffers', self, self.handler ); }, // Callback function for explain option "timing" button click on_explain_timing: function(ev) { var self = this; this._stopEventPropogation(ev); // Trigger the explain "timing" signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:explain-timing', self, self.handler ); }, do_not_close_menu: function(ev) { ev.stopPropagation(); }, // callback function for load file button click. on_file_load: function(ev) { var self = this; this._stopEventPropogation(ev); this._closeDropDown(ev); // Trigger the save signal to the SqlEditorController class self.handler.trigger( 'pgadmin-sqleditor:button:load_file', self, self.handler ); }, on_download: function() { queryToolActions.download(this.handler); }, keyAction: function(event) { var panel_id, self = this; panel_id = keyboardShortcuts.processEventQueryTool( this.handler, queryToolActions, event ); // If it return panel id then focus it if(!_.isNull(panel_id) && !_.isUndefined(panel_id)) { // Returned panel index, by incrementing it by 1 we will get actual panel panel_id++; this.docker.findPanels()[panel_id].focus(); // We set focus on history tab so we need to set the focus on // editor explicitly if(panel_id == 3) { setTimeout(function() { self.query_tool_obj.focus(); }, 100); } } }, // Callback function for the commit button click. on_commit_transaction: function() { queryToolActions.executeCommit(this.handler); }, // Callback function for the rollback button click. on_rollback_transaction: function() { queryToolActions.executeRollback(this.handler); }, }); /* Defining controller class for data grid, which actually * perform the operations like executing the sql query, poll the result, * render the data in the grid, Save/Refresh the data etc... */ var SqlEditorController = function() { this.initialize.apply(this, arguments); }; _.extend( SqlEditorController.prototype, Backbone.Events, { initialize: function(container) { var self = this; this.container = container; this.state = {}; this.csrf_token = pgAdmin.csrf_token; // Disable animation first modifyAnimation.modifyAlertifyAnimation(); if (!alertify.dlgGetServerPass) { alertify.dialog('dlgGetServerPass', function factory() { return { main: function( title, message ) { this.set('title', title); this.setting('message',message); }, setup:function() { return { buttons:[ { text: gettext('OK'), key: 13, className: 'btn btn-primary', }, { text: gettext('Cancel'), key: 27, className: 'btn btn-danger', }, ], focus: { element: '#password', select: true, }, options: { modal: 0, resizable: false, maximizable: false, pinnable: false, }, }; }, build:function() {}, settings:{ message: null, }, prepare:function() { this.setContent(this.setting('message')); }, callback: function(closeEvent) { if (closeEvent.button.text == gettext('OK')) { var passdata = $(this.elements.content).find('#frmPassword').serialize(); self.init_connection(false, passdata); } }, }; }); } this.on('pgadmin-datagrid:transaction:created', function(trans_obj) { self.transId = trans_obj.gridTransId; self.warn_before_continue(); }); pgBrowser.Events.on('pgadmin:user:logged-in', function() { self.initTransaction(); }); }, saveState: function(fn, args) { if (fn) { this.state = { 'fn': fn, 'args': args, }; } else { this.state = {}; } }, initTransaction: function() { var url_endpoint; if (this.is_query_tool) { url_endpoint = 'datagrid.initialize_query_tool'; // If database not present then use Maintenance database // We will handle this at server side if (this.url_params.did) { url_endpoint = 'datagrid.initialize_query_tool_with_did'; } } else { url_endpoint = 'datagrid.initialize_datagrid'; } var baseUrl = url_for(url_endpoint, this.url_params); Datagrid.create_transaction(baseUrl, this, this.is_query_tool, this.server_type, '', '', '', true); }, handle_connection_lost: function(create_transaction, xhr) { /* If responseJSON is undefined then it could be object of * axios(Promise HTTP) response, so we should check accordingly. */ if (xhr.responseJSON !== undefined && xhr.responseJSON.data && !xhr.responseJSON.data.conn_id) { // if conn_id is null then this is maintenance db. // so attempt connection connect without prompt. this.init_connection(create_transaction); } else if (xhr.data !== undefined && xhr.data.data && !xhr.data.data.conn_id) { // if conn_id is null then this is maintenance db. // so attempt connection connect without prompt. this.init_connection(create_transaction); } else { this.warn_before_continue(); } }, warn_before_continue: function() { var self = this; alertify.confirm( gettext('Connection Warning'), ''+ '
'+ ' '+ ''+
''+
gettext('The application has lost the database connection:')+
''+
'
'+
''+
gettext('⁃ If the connection was idle it may have been forcibly disconnected.')+
'
'+
gettext('⁃ The application server or database server may have been restarted.')+
'
'+
gettext('⁃ The user session may have timed out.')+
''+
'
'+
' '+
'