Skip to content

Commit 68e559c

Browse files
Added support to preserve the workspace, query windows, and pgAdmin state during an abrupt shutdown or restart. #3319
1 parent c2ef9d0 commit 68e559c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1001
-259
lines changed
14.1 KB
Loading

docs/en_US/preferences.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,12 +309,21 @@ Use the fields on the *User Interface* panel to set the user interface related p
309309
this setting is False, meaning that Query Tool/PSQL tabs will open in the currently
310310
active workspace (either the default or the workspace selected at the time of opening).
311311

312+
* When the *Save the application state?* option is enabled the current state of various
313+
tools—such as Query Tool, ERD, Schema Diff, and PSQL—will be saved in the encrypted
314+
format.If the application is closed unexpectedly, the tab is accidentally closed,
315+
or the page is refreshed, the saved state will be automatically restored for
316+
each respective tool.**Note:**
317+
312318
* Use the *Themes* drop-down listbox to select the theme for pgAdmin. You'll also get a preview just below the
313319
drop down. You can also submit your own themes,
314320
check `here <https://github.com/pgadmin-org/pgadmin4/blob/master/README.md>`_ how.
315321
Currently we support Light, Dark, High Contrast and System theme. Selecting System option will follow
316322
your computer's settings.
317323

324+
**Note:** Saving the application state will not preserve data for tool tabs opened in
325+
separate browser tabs when running in server mode..
326+
318327
The Paths Node
319328
**************
320329

web/migrations/versions/c62bcc14c3d6_.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ def upgrade():
4040
))
4141
)
4242

43+
op.create_table(
44+
'application_state',
45+
sa.Column('uid', sa.Integer(), nullable=False),
46+
sa.Column('id', sa.Integer()),
47+
sa.Column('connection_info', sa.JSON()),
48+
sa.Column('tool_name', sa.String(length=64)),
49+
sa.Column('tool_data', sa.String()),
50+
sa.ForeignKeyConstraint(['uid'], ['user.id'], ondelete='CASCADE'),
51+
sa.PrimaryKeyConstraint('id', 'uid'))
52+
4353

4454
def downgrade():
4555
# pgAdmin only upgrades, downgrade not implemented.

web/pgadmin/authenticate/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,6 @@ def authenticate(self):
292292
current_app.logger.debug(
293293
"Authentication initiated via source: %s is failed." %
294294
source.get_source_name())
295-
296295
return status, msg
297296

298297
def login(self):

web/pgadmin/browser/static/js/browser.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import _ from 'lodash';
1212
import { checkMasterPassword, showQuickSearch } from '../../../static/js/Dialogs/index';
1313
import { pgHandleItemError } from '../../../static/js/utils';
1414
import { send_heartbeat, stop_heartbeat } from './heartbeat';
15-
import getApiInstance from '../../../static/js/api_instance';
15+
import getApiInstance, {parseApiError} from '../../../static/js/api_instance';
1616
import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/static/js/store';
1717
import checkNodeVisibility from '../../../static/js/check_node_visibility';
18+
import * as showQueryTool from '../../../tools/sqleditor/static/js/show_query_tool';
19+
import {getRandomInt} from 'sources/utils';
1820

1921
define('pgadmin.browser', [
2022
'sources/gettext', 'sources/url_for', 'sources/pgadmin',
@@ -206,6 +208,12 @@ define('pgadmin.browser', [
206208
uiloaded: function() {
207209
this.set_master_password('');
208210
this.check_version_update();
211+
const prefStore = usePreferences.getState();
212+
let save_the_workspace = prefStore.getPreferencesForModule('misc').save_app_state;
213+
if(save_the_workspace){
214+
this.restore_pgadmin_state();
215+
pgBrowser.docker.default_workspace.focus();
216+
}
209217
},
210218
check_corrupted_db_file: function() {
211219
getApiInstance().get(
@@ -291,6 +299,42 @@ define('pgadmin.browser', [
291299
});
292300
},
293301

302+
restore_pgadmin_state: function () {
303+
getApiInstance({'Content-Encoding': 'gzip'}).get(
304+
url_for('settings.get_application_state')
305+
).then((res)=> {
306+
if(res.data.success && res.data.data.result.length > 0){
307+
_.each(res.data.data.result, function(toolState){
308+
let toolNme = toolState.tool_name;
309+
let toolDataId = `${toolNme}-${getRandomInt(1, 9999999)}`;
310+
let connectionInfo = toolState.connection_info;
311+
localStorage.setItem(toolDataId, toolState.tool_data);
312+
313+
if (toolNme == 'sqleditor'){
314+
showQueryTool.relaunchSqlTool(connectionInfo, toolDataId);
315+
}else if(toolNme == 'psql'){
316+
pgAdmin.Tools.Psql.openPsqlTool(null, null, connectionInfo);
317+
}else if(toolNme == 'ERD'){
318+
pgAdmin.Tools.ERD.showErdTool(null, null, false, connectionInfo, toolDataId);
319+
}else if(toolNme == 'schema_diff'){
320+
pgAdmin.Tools.SchemaDiff.launchSchemaDiff(toolDataId);
321+
}
322+
});
323+
324+
// call clear application state data.
325+
try {
326+
getApiInstance().delete(url_for('settings.delete_application_state'), {});
327+
} catch (error) {
328+
console.error(error);
329+
pgAdmin.Browser.notifier.error(gettext('Failed to remove query data.') + parseApiError(error));
330+
}
331+
}
332+
}).catch(function(error) {
333+
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
334+
getApiInstance().delete(url_for('settings.delete_application_state'), {});
335+
});
336+
},
337+
294338
bind_beforeunload: function() {
295339
window.addEventListener('beforeunload', function(e) {
296340
/* Can open you in new tab */

web/pgadmin/misc/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ def register_preferences(self):
124124
)
125125
)
126126

127+
self.preference.register(
128+
'user_interface', 'save_app_state',
129+
gettext("Save the application state?"),
130+
'boolean', True,
131+
category_label=PREF_LABEL_USER_INTERFACE,
132+
help_str=gettext(
133+
'If set to True, pgAdmin will save the state of opened tools'
134+
' (such as Query Tool, PSQL, Schema Diff, and ERD), including'
135+
' any unsaved data. This data will be automatically restored'
136+
' in the event of an unexpected shutdown or browser refresh.'
137+
)
138+
)
139+
127140
if not config.SERVER_MODE:
128141
self.preference.register(
129142
'file_downloads', 'automatically_open_downloaded_file',

web/pgadmin/misc/workspaces/static/js/AdHocConnection.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ export default function AdHocConnection({mode}) {
455455
'pgadmin:tool:show',
456456
`${BROWSER_PANELS.PSQL_TOOL}_${transId}`,
457457
openUrl,
458-
{title: escapedTitle, db: db_name},
458+
{title: escapedTitle, db: db_name, server_name: formData.server_name, 'user': user_name},
459459
{title: panelTitle, icon: 'pg-font-icon icon-terminal', manualClose: false, renamable: true},
460460
Boolean(open_new_tab?.includes('psql_tool'))
461461
);

web/pgadmin/misc/workspaces/static/js/WorkspaceProvider.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,16 @@ export function WorkspaceProvider({children}) {
7575
pgAdmin.Browser.docker.currentWorkspace = newVal;
7676
if (newVal == WORKSPACES.DEFAULT) {
7777
setTimeout(() => {
78-
pgAdmin.Browser.tree.selectNode(lastSelectedTreeItem.current, true, 'center');
78+
pgAdmin.Browser.tree?.selectNode(lastSelectedTreeItem.current, true, 'center');
7979
lastSelectedTreeItem.current = null;
8080
}, 250);
8181
} else {
8282
// Get the selected tree node and save it into the state variable.
83-
let selItem = pgAdmin.Browser.tree.selected();
83+
let selItem = pgAdmin.Browser.tree?.selected();
8484
if (selItem)
8585
lastSelectedTreeItem.current = selItem;
8686
// Deselect the node to disable the menu options.
87-
pgAdmin.Browser.tree.deselect(selItem);
87+
pgAdmin.Browser.tree?.deselect(selItem);
8888
}
8989
setCurrentWorkspace(newVal);
9090
};

web/pgadmin/model/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,17 @@ class QueryHistoryModel(db.Model):
392392
last_updated_flag = db.Column(db.String(), nullable=False)
393393

394394

395+
class ApplicationState(db.Model):
396+
"""Define the application state SQL table."""
397+
__tablename__ = 'application_state'
398+
uid = db.Column(db.Integer(), db.ForeignKey(USER_ID), nullable=False,
399+
primary_key=True)
400+
id = db.Column(db.Integer(),nullable=False,primary_key=True)
401+
connection_info = db.Column(MutableDict.as_mutable(types.JSON))
402+
tool_name = db.Column(db.String(64), nullable=False)
403+
tool_data = db.Column(PgAdminDbBinaryString())
404+
405+
395406
class Database(db.Model):
396407
"""
397408
Define a Database.

web/pgadmin/preferences/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import json
1717
from flask import render_template, Response, request, session, current_app
1818
from flask_babel import gettext
19+
20+
from pgadmin.settings import delete_tool_data
1921
from pgadmin.user_login_check import pga_login_required
2022
from pgadmin.utils import PgAdminModule
2123
from pgadmin.utils.ajax import success_return, \
@@ -238,6 +240,9 @@ def save():
238240
data['mid'], data['category_id'], data['id'], data['value'])
239241
sgm.get_nodes(sgm)
240242

243+
if data['name'] == 'save_app_state' and not data['value']:
244+
delete_tool_data()
245+
241246
if not res:
242247
return internal_server_error(errormsg=msg)
243248

0 commit comments

Comments
 (0)