Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/en_US/images/preferences_misc_user_interface.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions docs/en_US/preferences.rst
Original file line number Diff line number Diff line change
Expand Up @@ -309,12 +309,21 @@ Use the fields on the *User Interface* panel to set the user interface related p
this setting is False, meaning that Query Tool/PSQL tabs will open in the currently
active workspace (either the default or the workspace selected at the time of opening).

* When the *Save the application state?* option is enabled the current state of various
tools—such as Query Tool, ERD, Schema Diff, and PSQL—will be saved in the encrypted
format.If the application is closed unexpectedly, the tab is accidentally closed,
or the page is refreshed, the saved state will be automatically restored for
each respective tool.**Note:**

* Use the *Themes* drop-down listbox to select the theme for pgAdmin. You'll also get a preview just below the
drop down. You can also submit your own themes,
check `here <https://github.com/pgadmin-org/pgadmin4/blob/master/README.md>`_ how.
Currently we support Light, Dark, High Contrast and System theme. Selecting System option will follow
your computer's settings.

**Note:** Saving the application state will not preserve data for tool tabs opened in
separate browser tabs when running in server mode..

The Paths Node
**************

Expand Down
10 changes: 10 additions & 0 deletions web/migrations/versions/c62bcc14c3d6_.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ def upgrade():
))
)

op.create_table(
'application_state',
sa.Column('uid', sa.Integer(), nullable=False),
sa.Column('id', sa.Integer()),
sa.Column('connection_info', sa.JSON()),
sa.Column('tool_name', sa.String(length=64)),
sa.Column('tool_data', sa.String()),
sa.ForeignKeyConstraint(['uid'], ['user.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id', 'uid'))


def downgrade():
# pgAdmin only upgrades, downgrade not implemented.
Expand Down
1 change: 0 additions & 1 deletion web/pgadmin/authenticate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,6 @@ def authenticate(self):
current_app.logger.debug(
"Authentication initiated via source: %s is failed." %
source.get_source_name())

return status, msg

def login(self):
Expand Down
46 changes: 45 additions & 1 deletion web/pgadmin/browser/static/js/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import _ from 'lodash';
import { checkMasterPassword, showQuickSearch } from '../../../static/js/Dialogs/index';
import { pgHandleItemError } from '../../../static/js/utils';
import { send_heartbeat, stop_heartbeat } from './heartbeat';
import getApiInstance from '../../../static/js/api_instance';
import getApiInstance, {parseApiError} from '../../../static/js/api_instance';
import usePreferences, { setupPreferenceBroadcast } from '../../../preferences/static/js/store';
import checkNodeVisibility from '../../../static/js/check_node_visibility';
import * as showQueryTool from '../../../tools/sqleditor/static/js/show_query_tool';
import {getRandomInt} from 'sources/utils';

define('pgadmin.browser', [
'sources/gettext', 'sources/url_for', 'sources/pgadmin',
Expand Down Expand Up @@ -206,6 +208,12 @@ define('pgadmin.browser', [
uiloaded: function() {
this.set_master_password('');
this.check_version_update();
const prefStore = usePreferences.getState();
let save_the_workspace = prefStore.getPreferencesForModule('misc').save_app_state;
if(save_the_workspace){
this.restore_pgadmin_state();
pgBrowser.docker.default_workspace.focus();
}
},
check_corrupted_db_file: function() {
getApiInstance().get(
Expand Down Expand Up @@ -291,6 +299,42 @@ define('pgadmin.browser', [
});
},

restore_pgadmin_state: function () {
getApiInstance({'Content-Encoding': 'gzip'}).get(
url_for('settings.get_application_state')
).then((res)=> {
if(res.data.success && res.data.data.result.length > 0){
_.each(res.data.data.result, function(toolState){
let toolNme = toolState.tool_name;
let toolDataId = `${toolNme}-${getRandomInt(1, 9999999)}`;
let connectionInfo = toolState.connection_info;
localStorage.setItem(toolDataId, toolState.tool_data);

if (toolNme == 'sqleditor'){
showQueryTool.relaunchSqlTool(connectionInfo, toolDataId);
}else if(toolNme == 'psql'){
pgAdmin.Tools.Psql.openPsqlTool(null, null, connectionInfo);
}else if(toolNme == 'ERD'){
pgAdmin.Tools.ERD.showErdTool(null, null, false, connectionInfo, toolDataId);
}else if(toolNme == 'schema_diff'){
pgAdmin.Tools.SchemaDiff.launchSchemaDiff(toolDataId);
}
});

// call clear application state data.
try {
getApiInstance().delete(url_for('settings.delete_application_state'), {});
} catch (error) {
console.error(error);
pgAdmin.Browser.notifier.error(gettext('Failed to remove query data.') + parseApiError(error));
}
}
}).catch(function(error) {
pgAdmin.Browser.notifier.pgRespErrorNotify(error);
getApiInstance().delete(url_for('settings.delete_application_state'), {});
});
},

bind_beforeunload: function() {
window.addEventListener('beforeunload', function(e) {
/* Can open you in new tab */
Expand Down
13 changes: 13 additions & 0 deletions web/pgadmin/misc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ def register_preferences(self):
)
)

self.preference.register(
'user_interface', 'save_app_state',
gettext("Save the application state?"),
'boolean', True,
category_label=PREF_LABEL_USER_INTERFACE,
help_str=gettext(
'If set to True, pgAdmin will save the state of opened tools'
' (such as Query Tool, PSQL, Schema Diff, and ERD), including'
' any unsaved data. This data will be automatically restored'
' in the event of an unexpected shutdown or browser refresh.'
)
)

if not config.SERVER_MODE:
self.preference.register(
'file_downloads', 'automatically_open_downloaded_file',
Expand Down
2 changes: 1 addition & 1 deletion web/pgadmin/misc/workspaces/static/js/AdHocConnection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ export default function AdHocConnection({mode}) {
'pgadmin:tool:show',
`${BROWSER_PANELS.PSQL_TOOL}_${transId}`,
openUrl,
{title: escapedTitle, db: db_name},
{title: escapedTitle, db: db_name, server_name: formData.server_name, 'user': user_name},
{title: panelTitle, icon: 'pg-font-icon icon-terminal', manualClose: false, renamable: true},
Boolean(open_new_tab?.includes('psql_tool'))
);
Expand Down
6 changes: 3 additions & 3 deletions web/pgadmin/misc/workspaces/static/js/WorkspaceProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,16 @@ export function WorkspaceProvider({children}) {
pgAdmin.Browser.docker.currentWorkspace = newVal;
if (newVal == WORKSPACES.DEFAULT) {
setTimeout(() => {
pgAdmin.Browser.tree.selectNode(lastSelectedTreeItem.current, true, 'center');
pgAdmin.Browser.tree?.selectNode(lastSelectedTreeItem.current, true, 'center');
lastSelectedTreeItem.current = null;
}, 250);
} else {
// Get the selected tree node and save it into the state variable.
let selItem = pgAdmin.Browser.tree.selected();
let selItem = pgAdmin.Browser.tree?.selected();
if (selItem)
lastSelectedTreeItem.current = selItem;
// Deselect the node to disable the menu options.
pgAdmin.Browser.tree.deselect(selItem);
pgAdmin.Browser.tree?.deselect(selItem);
}
setCurrentWorkspace(newVal);
};
Expand Down
11 changes: 11 additions & 0 deletions web/pgadmin/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,17 @@ class QueryHistoryModel(db.Model):
last_updated_flag = db.Column(db.String(), nullable=False)


class ApplicationState(db.Model):
"""Define the application state SQL table."""
__tablename__ = 'application_state'
uid = db.Column(db.Integer(), db.ForeignKey(USER_ID), nullable=False,
primary_key=True)
id = db.Column(db.Integer(),nullable=False,primary_key=True)
connection_info = db.Column(MutableDict.as_mutable(types.JSON))
tool_name = db.Column(db.String(64), nullable=False)
tool_data = db.Column(PgAdminDbBinaryString())


class Database(db.Model):
"""
Define a Database.
Expand Down
5 changes: 5 additions & 0 deletions web/pgadmin/preferences/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import json
from flask import render_template, Response, request, session, current_app
from flask_babel import gettext

from pgadmin.settings import delete_tool_data
from pgadmin.user_login_check import pga_login_required
from pgadmin.utils import PgAdminModule
from pgadmin.utils.ajax import success_return, \
Expand Down Expand Up @@ -238,6 +240,9 @@ def save():
data['mid'], data['category_id'], data['id'], data['value'])
sgm.get_nodes(sgm)

if data['name'] == 'save_app_state' and not data['value']:
delete_tool_data()

if not res:
return internal_server_error(errormsg=msg)

Expand Down
Loading