Added support for viewing PostgreSQL Server Logs in Text, CSV and JSON formats. #3981
parent
93b5bc6bf8
commit
4f415f9768
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
After Width: | Height: | Size: 188 KiB |
Binary file not shown.
After Width: | Height: | Size: 284 KiB |
Binary file not shown.
After Width: | Height: | Size: 152 KiB |
|
@ -20,6 +20,7 @@ Bundled PostgreSQL Utilities
|
||||||
New features
|
New features
|
||||||
************
|
************
|
||||||
|
|
||||||
|
| `Issue #3981 <https://github.com/pgadmin-org/pgadmin4/issues/3981>`_ - Add support for Postgres Server Logs for Text, CSV and JSON format in plain and tabular formats. Upgrade React to version 18.
|
||||||
|
|
||||||
Housekeeping
|
Housekeeping
|
||||||
************
|
************
|
||||||
|
|
|
@ -9,14 +9,14 @@ display information about the object currently selected in the *pgAdmin* tree
|
||||||
control in the left window. Select a tab to access information about the
|
control in the left window. Select a tab to access information about the
|
||||||
highlighted object in the tree control.
|
highlighted object in the tree control.
|
||||||
|
|
||||||
.. image:: images/main_dashboard_general.png
|
.. image:: images/dashboard_activity.png
|
||||||
:alt: Dashboard panel
|
:alt: Dashboard Activity
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
The graphs and tables on the *Dashboard* tab provides an active analysis of system statistics and the usage
|
The graphs and tables on the *Dashboard* tab provides an active analysis of system statistics and the usage
|
||||||
statistics for the selected server or database.
|
statistics for the selected server or database.
|
||||||
|
|
||||||
Click the *General* tab to get the usage statistics for the selected server or database:
|
Click the *Activity* tab to get the usage statistics for the selected server or database:
|
||||||
|
|
||||||
* The *Server sessions* or *Database sessions* graph displays the interactions
|
* The *Server sessions* or *Database sessions* graph displays the interactions
|
||||||
with the server or database.
|
with the server or database.
|
||||||
|
@ -30,8 +30,14 @@ Click the *General* tab to get the usage statistics for the selected server or d
|
||||||
or fetched from the buffer cache (but not the operating system's file system
|
or fetched from the buffer cache (but not the operating system's file system
|
||||||
cache) for the server or database.
|
cache) for the server or database.
|
||||||
|
|
||||||
The *Server activity* panel displays information about sessions, locks, prepared
|
.. image:: images/dashboard_stat.png
|
||||||
transactions, and server configuration (if applicable). The information is
|
:alt: Dashboard Activity
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
Click the *Stat* tab to get the usage statistics for the selected server or database:
|
||||||
|
|
||||||
|
The *Stat* panel displays information about sessions, locks, prepared
|
||||||
|
transactions. The information is
|
||||||
presented in context-sensitive tables. Use controls located above the table to:
|
presented in context-sensitive tables. Use controls located above the table to:
|
||||||
|
|
||||||
* Click the *Refresh* button to update the information displayed in each table.
|
* Click the *Refresh* button to update the information displayed in each table.
|
||||||
|
@ -56,6 +62,22 @@ session:
|
||||||
* Use the *Details* icon (located in the third column) to open the *Details*
|
* Use the *Details* icon (located in the third column) to open the *Details*
|
||||||
tab; the tab displays information about the selected session.
|
tab; the tab displays information about the selected session.
|
||||||
|
|
||||||
|
.. image:: images/dashboard_config.png
|
||||||
|
:alt: Dashboard Activity
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
Click the *Configuration* tab to get the server configuration details.
|
||||||
|
|
||||||
|
|
||||||
|
.. image:: images/dashboard_logs.png
|
||||||
|
:alt: Dashboard Activity
|
||||||
|
:align: center
|
||||||
|
|
||||||
|
Click the *Logs* tab to get the server logs.
|
||||||
|
|
||||||
|
* Use the Log Format switch to select the format you want. Text/Plain, JSON and CSV are supported.
|
||||||
|
* Use the Logs in tabular format? switch if you want to see the logs in a tabular format.
|
||||||
|
|
||||||
Click the *System Statistics* tab to get the statistics for the system:
|
Click the *System Statistics* tab to get the statistics for the system:
|
||||||
|
|
||||||
.. image:: images/main_dashboard_sys_statistics_summary.png
|
.. image:: images/main_dashboard_sys_statistics_summary.png
|
||||||
|
|
|
@ -929,6 +929,12 @@ SERVER_HEARTBEAT_TIMEOUT = 30 # In seconds
|
||||||
#############################################################################
|
#############################################################################
|
||||||
ENABLE_SERVER_PASS_EXEC_CMD = False
|
ENABLE_SERVER_PASS_EXEC_CMD = False
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# Number of records to fetch in one batch for server logs.
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
ON_DEMAND_LOG_COUNT = 10000
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# Patch the default config with custom config and other manipulations
|
# Patch the default config with custom config and other manipulations
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
|
@ -21,9 +21,10 @@
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@emotion/utils": "^1.0.0",
|
"@emotion/utils": "^1.0.0",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@testing-library/jest-dom": "^6.1.2",
|
"@testing-library/dom": "10.2.0",
|
||||||
"@testing-library/react": "12",
|
"@testing-library/jest-dom": "^6.4.6",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/react": "16.0.0",
|
||||||
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/jest": "^29.5.4",
|
"@types/jest": "^29.5.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.12.0",
|
"@typescript-eslint/eslint-plugin": "^7.12.0",
|
||||||
"@typescript-eslint/parser": "^7.12.0",
|
"@typescript-eslint/parser": "^7.12.0",
|
||||||
|
@ -72,6 +73,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||||
"@babel/preset-react": "^7.12.13",
|
"@babel/preset-react": "^7.12.13",
|
||||||
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@codemirror/lang-sql": "^6.6.5",
|
"@codemirror/lang-sql": "^6.6.5",
|
||||||
"@date-io/core": "^3.0.0",
|
"@date-io/core": "^3.0.0",
|
||||||
"@date-io/date-fns": "3.x",
|
"@date-io/date-fns": "3.x",
|
||||||
|
@ -84,10 +86,12 @@
|
||||||
"@projectstorm/react-diagrams": "^6.6.1",
|
"@projectstorm/react-diagrams": "^6.6.1",
|
||||||
"@simonwep/pickr": "^1.5.1",
|
"@simonwep/pickr": "^1.5.1",
|
||||||
"@szhsin/react-menu": "^2.2.0",
|
"@szhsin/react-menu": "^2.2.0",
|
||||||
|
"@tanstack/react-query": "5.37.1",
|
||||||
"@tanstack/react-table": "^8.16.0",
|
"@tanstack/react-table": "^8.16.0",
|
||||||
"@tanstack/react-virtual": "^3.4.0",
|
"@tanstack/react-virtual": "^3.4.0",
|
||||||
"@types/react": "^17.0.80",
|
"@types/classnames": "^2.2.6",
|
||||||
"@types/react-dom": "^17.0.25",
|
"@types/react": "^18.0.2",
|
||||||
|
"@types/react-dom": "^18.0.0",
|
||||||
"ajv": "^8.8.2",
|
"ajv": "^8.8.2",
|
||||||
"anti-trojan-source": "^1.4.0",
|
"anti-trojan-source": "^1.4.0",
|
||||||
"aspen-decorations": "^1.0.2",
|
"aspen-decorations": "^1.0.2",
|
||||||
|
@ -125,14 +129,14 @@
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"raf": "^3.4.1",
|
"raf": "^3.4.1",
|
||||||
"rc-dock": "^3.2.9",
|
"rc-dock": "^3.2.9",
|
||||||
"react": "^17.0.1",
|
"react": "^18.2.0",
|
||||||
"react-arborist": "^3.2.0",
|
"react-arborist": "^3.2.0",
|
||||||
"react-aspen": "^1.1.0",
|
"react-aspen": "^1.1.0",
|
||||||
"react-checkbox-tree": "^1.7.2",
|
"react-checkbox-tree": "^1.7.2",
|
||||||
"react-data-grid": "https://github.com/pgadmin-org/react-data-grid.git#200d2f5e02de694e3e9ffbe177c279bc40240fb8",
|
"react-data-grid": "https://github.com/pgadmin-org/react-data-grid.git#200d2f5e02de694e3e9ffbe177c279bc40240fb8",
|
||||||
"react-dnd": "^16.0.1",
|
"react-dnd": "^16.0.1",
|
||||||
"react-dnd-html5-backend": "^16.0.1",
|
"react-dnd-html5-backend": "^16.0.1",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^18.2.0",
|
||||||
"react-draggable": "^4.4.6",
|
"react-draggable": "^4.4.6",
|
||||||
"react-dropzone": "^14.2.1",
|
"react-dropzone": "^14.2.1",
|
||||||
"react-frame-component": "^5.2.6",
|
"react-frame-component": "^5.2.6",
|
||||||
|
|
|
@ -382,7 +382,7 @@ define('pgadmin.browser.node', [
|
||||||
|
|
||||||
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(nodeItem);
|
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(nodeItem);
|
||||||
const panelId = _.uniqueId(BROWSER_PANELS.EDIT_PROPERTIES);
|
const panelId = _.uniqueId(BROWSER_PANELS.EDIT_PROPERTIES);
|
||||||
const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);
|
const onClose = (force=false)=>{ pgBrowser.docker.close(panelId, force); };
|
||||||
const onSave = (newNodeData)=>{
|
const onSave = (newNodeData)=>{
|
||||||
// Clear the cache for this node now.
|
// Clear the cache for this node now.
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
|
@ -412,7 +412,7 @@ define('pgadmin.browser.node', [
|
||||||
// browser tree upon the 'Save' button click.
|
// browser tree upon the 'Save' button click.
|
||||||
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(nodeItem);
|
treeNodeInfo = pgBrowser.tree.getTreeNodeHierarchy(nodeItem);
|
||||||
const panelId = _.uniqueId(BROWSER_PANELS.EDIT_PROPERTIES);
|
const panelId = _.uniqueId(BROWSER_PANELS.EDIT_PROPERTIES);
|
||||||
const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);
|
const onClose = (force=false)=>{ pgBrowser.docker.close(panelId, force); };
|
||||||
const onSave = (newNodeData)=>{
|
const onSave = (newNodeData)=>{
|
||||||
// Clear the cache for this node now.
|
// Clear the cache for this node now.
|
||||||
setTimeout(()=>{
|
setTimeout(()=>{
|
||||||
|
@ -438,7 +438,7 @@ define('pgadmin.browser.node', [
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const panelId = BROWSER_PANELS.EDIT_PROPERTIES+nodeData.id;
|
const panelId = BROWSER_PANELS.EDIT_PROPERTIES+nodeData.id;
|
||||||
const onClose = (force=false)=>pgBrowser.docker.close(panelId, force);
|
const onClose = (force=false)=>{ pgBrowser.docker.close(panelId, force); };
|
||||||
const onSave = (newNodeData)=>{
|
const onSave = (newNodeData)=>{
|
||||||
let _old = nodeData,
|
let _old = nodeData,
|
||||||
_new = newNodeData.node,
|
_new = newNodeData.node,
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
"""A blueprint module implementing the dashboard frame."""
|
"""A blueprint module implementing the dashboard frame."""
|
||||||
import math
|
import math
|
||||||
|
import re
|
||||||
|
|
||||||
from flask import render_template, Response, g, request
|
from flask import render_template, Response, g, request
|
||||||
from flask_babel import gettext
|
from flask_babel import gettext
|
||||||
|
@ -16,8 +17,7 @@ from pgadmin.user_login_check import pga_login_required
|
||||||
import json
|
import json
|
||||||
from pgadmin.utils import PgAdminModule
|
from pgadmin.utils import PgAdminModule
|
||||||
from pgadmin.utils.ajax import make_response as ajax_response,\
|
from pgadmin.utils.ajax import make_response as ajax_response,\
|
||||||
internal_server_error
|
internal_server_error, make_json_response, precondition_required
|
||||||
|
|
||||||
from pgadmin.utils.driver import get_driver
|
from pgadmin.utils.driver import get_driver
|
||||||
from pgadmin.utils.preferences import Preferences
|
from pgadmin.utils.preferences import Preferences
|
||||||
from pgadmin.utils.constants import PREF_LABEL_DISPLAY, MIMETYPE_APP_JS, \
|
from pgadmin.utils.constants import PREF_LABEL_DISPLAY, MIMETYPE_APP_JS, \
|
||||||
|
@ -25,7 +25,7 @@ from pgadmin.utils.constants import PREF_LABEL_DISPLAY, MIMETYPE_APP_JS, \
|
||||||
|
|
||||||
from .precondition import check_precondition
|
from .precondition import check_precondition
|
||||||
from .pgd_replication import blueprint as pgd_replication
|
from .pgd_replication import blueprint as pgd_replication
|
||||||
from config import PG_DEFAULT_DRIVER
|
from config import PG_DEFAULT_DRIVER, ON_DEMAND_LOG_COUNT
|
||||||
|
|
||||||
MODULE_NAME = 'dashboard'
|
MODULE_NAME = 'dashboard'
|
||||||
|
|
||||||
|
@ -157,17 +157,17 @@ class DashboardModule(PgAdminModule):
|
||||||
|
|
||||||
self.display_graphs = self.dashboard_preference.register(
|
self.display_graphs = self.dashboard_preference.register(
|
||||||
'display', 'show_graphs',
|
'display', 'show_graphs',
|
||||||
gettext("Show graphs?"), 'boolean', True,
|
gettext("Show activity?"), 'boolean', True,
|
||||||
category_label=PREF_LABEL_DISPLAY,
|
category_label=PREF_LABEL_DISPLAY,
|
||||||
help_str=gettext('If set to True, graphs '
|
help_str=gettext('If set to True, activity '
|
||||||
'will be displayed on dashboards.')
|
'will be displayed on dashboards.')
|
||||||
)
|
)
|
||||||
|
|
||||||
self.display_server_activity = self.dashboard_preference.register(
|
self.display_server_activity = self.dashboard_preference.register(
|
||||||
'display', 'show_activity',
|
'display', 'show_activity',
|
||||||
gettext("Show activity?"), 'boolean', True,
|
gettext("Show state?"), 'boolean', True,
|
||||||
category_label=PREF_LABEL_DISPLAY,
|
category_label=PREF_LABEL_DISPLAY,
|
||||||
help_str=gettext('If set to True, activity tables '
|
help_str=gettext('If set to True, state tables '
|
||||||
'will be displayed on dashboards.')
|
'will be displayed on dashboards.')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -241,7 +241,9 @@ class DashboardModule(PgAdminModule):
|
||||||
'dashboard.get_prepared_by_server_id',
|
'dashboard.get_prepared_by_server_id',
|
||||||
'dashboard.get_prepared_by_database_id',
|
'dashboard.get_prepared_by_database_id',
|
||||||
'dashboard.config',
|
'dashboard.config',
|
||||||
'dashboard.get_config_by_server_id',
|
'dashboard.log_formats',
|
||||||
|
'dashboard.logs',
|
||||||
|
'dashboard.get_logs_by_server_id',
|
||||||
'dashboard.check_system_statistics',
|
'dashboard.check_system_statistics',
|
||||||
'dashboard.check_system_statistics_sid',
|
'dashboard.check_system_statistics_sid',
|
||||||
'dashboard.check_system_statistics_did',
|
'dashboard.check_system_statistics_did',
|
||||||
|
@ -318,7 +320,8 @@ def index(sid=None, did=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_data(sid, did, template, check_long_running_query=False):
|
def get_data(sid, did, template, check_long_running_query=False,
|
||||||
|
only_data=False):
|
||||||
"""
|
"""
|
||||||
Generic function to get server stats based on an SQL template
|
Generic function to get server stats based on an SQL template
|
||||||
Args:
|
Args:
|
||||||
|
@ -347,6 +350,9 @@ def get_data(sid, did, template, check_long_running_query=False):
|
||||||
if check_long_running_query:
|
if check_long_running_query:
|
||||||
get_long_running_query_status(res['rows'])
|
get_long_running_query_status(res['rows'])
|
||||||
|
|
||||||
|
if only_data:
|
||||||
|
return res['rows']
|
||||||
|
|
||||||
return ajax_response(
|
return ajax_response(
|
||||||
response=res['rows'],
|
response=res['rows'],
|
||||||
status=200
|
status=200
|
||||||
|
@ -431,7 +437,15 @@ def activity(sid=None, did=None):
|
||||||
:param sid: server id
|
:param sid: server id
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
return get_data(sid, did, 'activity.sql', True)
|
data = [{
|
||||||
|
'activity': get_data(sid, did, 'activity.sql', True, True),
|
||||||
|
'locks': get_data(sid, did, 'locks.sql', only_data=True),
|
||||||
|
'prepared': get_data(sid, did, 'prepared.sql', only_data=True)
|
||||||
|
}]
|
||||||
|
return ajax_response(
|
||||||
|
response=data,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/locks/', endpoint='locks')
|
@blueprint.route('/locks/', endpoint='locks')
|
||||||
|
@ -466,8 +480,7 @@ def prepared(sid=None, did=None):
|
||||||
return get_data(sid, did, 'prepared.sql')
|
return get_data(sid, did, 'prepared.sql')
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/config/', endpoint='config')
|
@blueprint.route('/config/<int:sid>', endpoint='config')
|
||||||
@blueprint.route('/config/<int:sid>', endpoint='get_config_by_server_id')
|
|
||||||
@pga_login_required
|
@pga_login_required
|
||||||
@check_precondition
|
@check_precondition
|
||||||
def config(sid=None):
|
def config(sid=None):
|
||||||
|
@ -479,6 +492,145 @@ def config(sid=None):
|
||||||
return get_data(sid, None, 'config.sql')
|
return get_data(sid, None, 'config.sql')
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/log_formats', endpoint='log_formats')
|
||||||
|
@blueprint.route('/log_formats/<int:sid>', endpoint='log_formats')
|
||||||
|
@pga_login_required
|
||||||
|
@check_precondition
|
||||||
|
def log_formats(sid=None):
|
||||||
|
if not sid:
|
||||||
|
return internal_server_error(errormsg='Server ID not specified.')
|
||||||
|
|
||||||
|
sql = render_template(
|
||||||
|
"/".join([g.template_path, 'log_format.sql'])
|
||||||
|
)
|
||||||
|
status, _format = g.conn.execute_scalar(sql)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=_format)
|
||||||
|
|
||||||
|
return ajax_response(
|
||||||
|
response=_format,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/logs/<log_format>/<disp_format>/<int:sid>', endpoint='logs')
|
||||||
|
@blueprint.route('/logs/<log_format>/<disp_format>/<int:sid>/<int:page>',
|
||||||
|
endpoint='get_logs_by_server_id')
|
||||||
|
@pga_login_required
|
||||||
|
@check_precondition
|
||||||
|
def logs(log_format=None, disp_format=None, sid=None, page=0):
|
||||||
|
"""
|
||||||
|
This function returns server logs details
|
||||||
|
"""
|
||||||
|
|
||||||
|
LOG_STATEMENTS = 'DEBUG:|STATEMENT:|LOG:|WARNING:|NOTICE:|INFO:' \
|
||||||
|
'|ERROR:|FATAL:|PANIC:'
|
||||||
|
if not sid:
|
||||||
|
return internal_server_error(
|
||||||
|
errormsg=gettext('Server ID not specified.'))
|
||||||
|
|
||||||
|
sql = render_template(
|
||||||
|
"/".join([g.template_path, 'log_format.sql'])
|
||||||
|
)
|
||||||
|
status, _format = g.conn.execute_scalar(sql)
|
||||||
|
|
||||||
|
# Check the requested format is available or not
|
||||||
|
log_format = ''
|
||||||
|
if log_format == 'C' and 'csvlog' in _format:
|
||||||
|
log_format = 'csvlog'
|
||||||
|
elif log_format == 'J' and 'jsonlog' in _format:
|
||||||
|
log_format = 'jsonlog'
|
||||||
|
|
||||||
|
sql = render_template(
|
||||||
|
"/".join([g.template_path, 'log_stat.sql']),
|
||||||
|
log_format=log_format, conn=g.conn
|
||||||
|
)
|
||||||
|
status, res = g.conn.execute_scalar(sql)
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
if not res or len(res) < 0:
|
||||||
|
return ajax_response(
|
||||||
|
response={'logs_disabled': True},
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
file_stat = json.loads(res[0])
|
||||||
|
|
||||||
|
_start = 0
|
||||||
|
_end = ON_DEMAND_LOG_COUNT
|
||||||
|
page = int(page)
|
||||||
|
final_cols = []
|
||||||
|
if page > 0:
|
||||||
|
_start = page * int(ON_DEMAND_LOG_COUNT)
|
||||||
|
_end = _start + int(ON_DEMAND_LOG_COUNT)
|
||||||
|
|
||||||
|
if _start < file_stat:
|
||||||
|
if disp_format == 'plain':
|
||||||
|
_end = file_stat
|
||||||
|
sql = render_template(
|
||||||
|
"/".join([g.template_path, 'logs.sql']), st=_start, ed=_end,
|
||||||
|
log_format=log_format, conn=g.conn
|
||||||
|
)
|
||||||
|
status, res = g.conn.execute_dict(sql)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
return internal_server_error(errormsg=res)
|
||||||
|
|
||||||
|
final_res = res['rows'][0]['pg_read_file'].split('\n')
|
||||||
|
# Json format
|
||||||
|
if log_format == 'J':
|
||||||
|
for f in final_res:
|
||||||
|
try:
|
||||||
|
_tmp_log = json.loads(f)
|
||||||
|
final_cols.append(
|
||||||
|
{"error_severity": _tmp_log['error_severity'],
|
||||||
|
"timestamp": _tmp_log['timestamp'],
|
||||||
|
"message": _tmp_log['message']})
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# CSV format
|
||||||
|
elif log_format == 'C':
|
||||||
|
for f in final_res:
|
||||||
|
try:
|
||||||
|
_tmp_log = f.split(',')
|
||||||
|
final_cols.append({"error_severity": _tmp_log[11],
|
||||||
|
"timestamp": _tmp_log[0],
|
||||||
|
"message": _tmp_log[13]})
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
col1 = []
|
||||||
|
col2 = []
|
||||||
|
_pattern = re.compile(LOG_STATEMENTS)
|
||||||
|
for f in final_res:
|
||||||
|
tmp = re.search(LOG_STATEMENTS, f)
|
||||||
|
if not tmp or tmp.group((0)) == 'STATEMENT:':
|
||||||
|
last_val = final_cols.pop() if len(final_cols) > 0\
|
||||||
|
else {'message': ''}
|
||||||
|
last_val['message'] += f
|
||||||
|
final_cols.append(last_val)
|
||||||
|
else:
|
||||||
|
_tmp = re.split(LOG_STATEMENTS, f)
|
||||||
|
|
||||||
|
final_cols.append({
|
||||||
|
"error_severity": tmp.group(0)[:len(tmp.group(0)) - 1],
|
||||||
|
"timestamp": _tmp[0],
|
||||||
|
"message": _tmp[1] if len(_tmp) > 1 else ''})
|
||||||
|
|
||||||
|
if disp_format == 'plain':
|
||||||
|
final_response = res['rows']
|
||||||
|
else:
|
||||||
|
final_response = final_cols
|
||||||
|
|
||||||
|
return ajax_response(
|
||||||
|
response=final_response,
|
||||||
|
status=200
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route(
|
@blueprint.route(
|
||||||
'/cancel_query/<int:sid>/<int:pid>', methods=['DELETE']
|
'/cancel_query/<int:sid>/<int:pid>', methods=['DELETE']
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
// This software is released under the PostgreSQL Licence
|
// This software is released under the PostgreSQL Licence
|
||||||
//
|
//
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState, Fragment } from 'react';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import gettext from 'sources/gettext';
|
import gettext from 'sources/gettext';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import getApiInstance from 'sources/api_instance';
|
import getApiInstance from 'sources/api_instance';
|
||||||
import PgTable from 'sources/components/PgTable';
|
import PgTable from 'sources/components/PgTable';
|
||||||
import { InputCheckbox } from '../../../static/js/components/FormComponents';
|
import { InputCheckbox, FormInputSwitch, FormInputToggle } from '../../../static/js/components/FormComponents';
|
||||||
import url_for from 'sources/url_for';
|
import url_for from 'sources/url_for';
|
||||||
import Graphs from './Graphs';
|
import Graphs from './Graphs';
|
||||||
import { Box, Tab, Tabs } from '@mui/material';
|
import { Box, Tab, Tabs } from '@mui/material';
|
||||||
|
@ -21,6 +21,7 @@ import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import StopSharpIcon from '@mui/icons-material/StopSharp';
|
import StopSharpIcon from '@mui/icons-material/StopSharp';
|
||||||
import WelcomeDashboard from './WelcomeDashboard';
|
import WelcomeDashboard from './WelcomeDashboard';
|
||||||
import ActiveQuery from './ActiveQuery.ui';
|
import ActiveQuery from './ActiveQuery.ui';
|
||||||
|
import ServerLog from './ServerLog.ui';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage';
|
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage';
|
||||||
import TabPanel from '../../../static/js/components/TabPanel';
|
import TabPanel from '../../../static/js/components/TabPanel';
|
||||||
|
@ -36,8 +37,11 @@ import ErrorBoundary from '../../../static/js/helpers/ErrorBoundary';
|
||||||
import { parseApiError } from '../../../static/js/api_instance';
|
import { parseApiError } from '../../../static/js/api_instance';
|
||||||
import SectionContainer from './components/SectionContainer';
|
import SectionContainer from './components/SectionContainer';
|
||||||
import Replication from './Replication';
|
import Replication from './Replication';
|
||||||
import RefreshButton from './components/RefreshButtons';
|
|
||||||
import { getExpandCell } from '../../../static/js/components/PgReactTableStyled';
|
import { getExpandCell } from '../../../static/js/components/PgReactTableStyled';
|
||||||
|
import CodeMirror from '../../../static/js/components/ReactCodeMirror';
|
||||||
|
import GetAppRoundedIcon from '@mui/icons-material/GetAppRounded';
|
||||||
|
import { getBrowser } from '../../../static/js/utils';
|
||||||
|
import RefreshButton from './components/RefreshButtons';
|
||||||
|
|
||||||
function parseData(data) {
|
function parseData(data) {
|
||||||
let res = [];
|
let res = [];
|
||||||
|
@ -47,7 +51,6 @@ function parseData(data) {
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Root = styled('div')(({theme}) => ({
|
const Root = styled('div')(({theme}) => ({
|
||||||
height: '100%',
|
height: '100%',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
@ -71,6 +74,16 @@ const Root = styled('div')(({theme}) => ({
|
||||||
'& .Dashboard-terminateButton': {
|
'& .Dashboard-terminateButton': {
|
||||||
color: theme.palette.error.main
|
color: theme.palette.error.main
|
||||||
},
|
},
|
||||||
|
'& .Dashboard-download': {
|
||||||
|
alignSelf: 'end',
|
||||||
|
'& .Dashboard-downloadButton': {
|
||||||
|
width: '35px',
|
||||||
|
height:'30px'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'& .Dashboard-textArea': {
|
||||||
|
height: '88%',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -85,6 +98,7 @@ const Root = styled('div')(({theme}) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let activeQSchemaObj = new ActiveQuery();
|
let activeQSchemaObj = new ActiveQuery();
|
||||||
|
let serverLogSchemaObj = new ServerLog();
|
||||||
|
|
||||||
const cellPropTypes = {
|
const cellPropTypes = {
|
||||||
row: PropTypes.any,
|
row: PropTypes.any,
|
||||||
|
@ -218,8 +232,25 @@ function getCancelCell(pgAdmin, sid, did, canTakeAction, onSuccess) {
|
||||||
return CancelCell;
|
return CancelCell;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ActiveOnlyHeader({activeOnly, setActiveOnly}) {
|
function CustomRefresh({refresh, setRefresh}) {
|
||||||
return (
|
return (
|
||||||
|
<RefreshButton onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
CustomRefresh.propTypes = {
|
||||||
|
refresh: PropTypes.bool,
|
||||||
|
setRefresh: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ActiveOnlyHeader({activeOnly, setActiveOnly, refresh, setRefresh}) {
|
||||||
|
return (<Fragment>
|
||||||
|
<RefreshButton onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setRefresh(!refresh);
|
||||||
|
}}/>
|
||||||
<InputCheckbox
|
<InputCheckbox
|
||||||
label={gettext('Active sessions only')}
|
label={gettext('Active sessions only')}
|
||||||
labelPlacement="end"
|
labelPlacement="end"
|
||||||
|
@ -232,35 +263,49 @@ function ActiveOnlyHeader({activeOnly, setActiveOnly}) {
|
||||||
controlProps={{
|
controlProps={{
|
||||||
label: gettext('Active sessions only'),
|
label: gettext('Active sessions only'),
|
||||||
}}
|
}}
|
||||||
/>
|
/></Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ActiveOnlyHeader.propTypes = {
|
ActiveOnlyHeader.propTypes = {
|
||||||
activeOnly: PropTypes.bool,
|
activeOnly: PropTypes.bool,
|
||||||
setActiveOnly: PropTypes.func,
|
setActiveOnly: PropTypes.func,
|
||||||
|
refresh: PropTypes.bool,
|
||||||
|
setRefresh: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
function Dashboard({
|
function Dashboard({
|
||||||
nodeItem, nodeData, node, treeNodeInfo,
|
nodeItem, nodeData, node, treeNodeInfo,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
|
const preferences = _.merge(
|
||||||
|
usePreferences().getPreferencesForModule('dashboards'),
|
||||||
|
usePreferences().getPreferencesForModule('graphs'),
|
||||||
|
usePreferences().getPreferencesForModule('misc')
|
||||||
|
);
|
||||||
|
|
||||||
let tabs = [gettext('Sessions'), gettext('Locks'), gettext('Prepared Transactions')];
|
// Set Active tab depending on preferences setting
|
||||||
let mainTabs = [gettext('General'), gettext('System Statistics')];
|
let activeTab = 0;
|
||||||
if(treeNodeInfo?.server?.replication_type) {
|
if (!_.isUndefined(preferences) && !preferences.show_graphs && preferences.show_activity) activeTab = 1;
|
||||||
mainTabs.push(gettext('Replication'));
|
else if (!_.isUndefined(preferences) && !preferences.show_graphs && !preferences.show_activity) activeTab = 2;
|
||||||
}
|
|
||||||
let systemStatsTabs = [gettext('Summary'), gettext('CPU'), gettext('Memory'), gettext('Storage')];
|
const api = getApiInstance();
|
||||||
const [dashData, setDashData] = useState([]);
|
const [dashData, setDashData] = useState([]);
|
||||||
const [msg, setMsg] = useState('');
|
const [msg, setMsg] = useState('');
|
||||||
const [ssMsg, setSsMsg] = useState('');
|
const [ssMsg, setSsMsg] = useState('');
|
||||||
const [tabVal, setTabVal] = useState(0);
|
const [mainTabVal, setMainTabVal] = useState(activeTab);
|
||||||
const [mainTabVal, setMainTabVal] = useState(0);
|
|
||||||
const [refresh, setRefresh] = useState(false);
|
const [refresh, setRefresh] = useState(false);
|
||||||
const [activeOnly, setActiveOnly] = useState(false);
|
const [activeOnly, setActiveOnly] = useState(false);
|
||||||
const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
|
const [systemStatsTabVal, setSystemStatsTabVal] = useState(0);
|
||||||
const [ldid, setLdid] = useState(0);
|
const [ldid, setLdid] = useState(0);
|
||||||
|
|
||||||
|
const [logCol, setLogCol] = useState(false);
|
||||||
|
const [logFormat, setLogFormat] = useState('T');
|
||||||
|
const [logConfigFormat, setLogConfigFormat] = useState([]);
|
||||||
|
const [nextPage, setNextPage] = useState(0);
|
||||||
|
const [hasNextPage, setHasNextPage] = useState(true);
|
||||||
|
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
const systemStatsTabChanged = (e, tabVal) => {
|
const systemStatsTabChanged = (e, tabVal) => {
|
||||||
setSystemStatsTabVal(tabVal);
|
setSystemStatsTabVal(tabVal);
|
||||||
};
|
};
|
||||||
|
@ -270,19 +315,13 @@ function Dashboard({
|
||||||
const dbConnected = treeNodeInfo?.database?.connected ?? false;
|
const dbConnected = treeNodeInfo?.database?.connected ?? false;
|
||||||
const serverConnected = treeNodeInfo?.server?.connected ?? false;
|
const serverConnected = treeNodeInfo?.server?.connected ?? false;
|
||||||
const prefStore = usePreferences();
|
const prefStore = usePreferences();
|
||||||
const preferences = _.merge(
|
let mainTabs = [gettext('Activity'), gettext('State')];
|
||||||
usePreferences().getPreferencesForModule('dashboards'),
|
|
||||||
usePreferences().getPreferencesForModule('graphs'),
|
|
||||||
usePreferences().getPreferencesForModule('misc')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!did) {
|
mainTabs.push(gettext('Configuration'), gettext('Logs'), gettext('System'));
|
||||||
tabs.push(gettext('Configuration'));
|
if(treeNodeInfo?.server?.replication_type) {
|
||||||
|
mainTabs.push(gettext('Replication'));
|
||||||
}
|
}
|
||||||
|
let systemStatsTabs = [gettext('Summary'), gettext('CPU'), gettext('Memory'), gettext('Storage')];
|
||||||
const tabChanged = (e, tabVal) => {
|
|
||||||
setTabVal(tabVal);
|
|
||||||
};
|
|
||||||
|
|
||||||
const mainTabChanged = (e, tabVal) => {
|
const mainTabChanged = (e, tabVal) => {
|
||||||
setMainTabVal(tabVal);
|
setMainTabVal(tabVal);
|
||||||
|
@ -390,6 +429,97 @@ function Dashboard({
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const downloadServerLogs = async () => {
|
||||||
|
let extension = '.txt',
|
||||||
|
type = 'plain',
|
||||||
|
respData = '';
|
||||||
|
|
||||||
|
if(logCol === false) {
|
||||||
|
if (logFormat == 'C') {
|
||||||
|
extension = '.csv';
|
||||||
|
type = 'csv';
|
||||||
|
} else if (logFormat == 'J') {
|
||||||
|
extension = '.json';
|
||||||
|
type = 'json';
|
||||||
|
}
|
||||||
|
respData = dashData[0]['pg_read_file'];
|
||||||
|
} else if (logCol === true) {
|
||||||
|
extension = '.csv';
|
||||||
|
type = 'csv';
|
||||||
|
respData = dashData.map((d)=> {return Object.values(d).join(','); }).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName = 'data-' + new Date().getTime() + extension;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let respBlob = new Blob([respData], {type : 'text/'+type}),
|
||||||
|
urlCreator = window.URL || window.webkitURL,
|
||||||
|
download_url = urlCreator.createObjectURL(respBlob),
|
||||||
|
link = document.createElement('a');
|
||||||
|
|
||||||
|
document.body.appendChild(link);
|
||||||
|
|
||||||
|
if (getBrowser() == 'IE' && window.navigator.msSaveBlob) {
|
||||||
|
// IE10: (has Blob, but not a[download] or URL)
|
||||||
|
window.navigator.msSaveBlob(respBlob, fileName);
|
||||||
|
} else {
|
||||||
|
link.setAttribute('href', download_url);
|
||||||
|
link.setAttribute('download', fileName);
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
document.body.removeChild(link);
|
||||||
|
} catch (error) {
|
||||||
|
setSsMsg(gettext('Failed to download the logs.'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const serverLogColumns = [
|
||||||
|
{
|
||||||
|
header: () => null,
|
||||||
|
enableSorting: false,
|
||||||
|
enableResizing: false,
|
||||||
|
enableFilters: false,
|
||||||
|
size: 35,
|
||||||
|
maxSize: 35,
|
||||||
|
minSize: 35,
|
||||||
|
id: 'btn-edit',
|
||||||
|
cell: getExpandCell({
|
||||||
|
title: gettext('View the log details')
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'error_severity',
|
||||||
|
header: gettext('Error Severity'),
|
||||||
|
enableSorting: true,
|
||||||
|
enableResizing: true,
|
||||||
|
enableFilters: true,
|
||||||
|
size: 100,
|
||||||
|
minSize: 35,
|
||||||
|
filterFn: 'equalsString'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'timestamp',
|
||||||
|
header: gettext('Log Prefix/Timestamp'),
|
||||||
|
sortable: true,
|
||||||
|
enableResizing: true,
|
||||||
|
enableSorting: false,
|
||||||
|
enableFilters: true,
|
||||||
|
size: 150,
|
||||||
|
minSize: 35,
|
||||||
|
filterFn: 'equalsString'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'message',
|
||||||
|
header: gettext('Logs'),
|
||||||
|
enableResizing: true,
|
||||||
|
enableSorting: false,
|
||||||
|
enableFilters: false,
|
||||||
|
size: 35,
|
||||||
|
minSize: 200,
|
||||||
|
filterFn: 'equalsString'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const activityColumns = [
|
const activityColumns = [
|
||||||
{
|
{
|
||||||
header: () => null,
|
header: () => null,
|
||||||
|
@ -682,15 +812,37 @@ function Dashboard({
|
||||||
enableFilters: true,
|
enableFilters: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
useEffect(() => {
|
||||||
|
if (mainTabVal == 3) {
|
||||||
|
setLogFormat('T');
|
||||||
|
let url = url_for('dashboard.log_formats') + '/' + sid;
|
||||||
|
api({
|
||||||
|
url: url,
|
||||||
|
type: 'GET',
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
let _format = res.data;
|
||||||
|
let _frm = [
|
||||||
|
{'label': gettext('Text'), 'value': 'T', 'disabled': !_format.includes('stderr')},
|
||||||
|
{'label': gettext('JSON'), 'value': 'J', 'disabled': !_format.includes('jsonlog')},
|
||||||
|
{'label': gettext('CSV'), 'value': 'C', 'disabled': !_format.includes('csvlog')}
|
||||||
|
];
|
||||||
|
setLogConfigFormat(_frm);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
pgAdmin.Browser.notifier.alert(
|
||||||
|
gettext('Failed to retrieve data from the server.'),
|
||||||
|
_.isUndefined(error.response) ? error.message : error.response.data.errormsg
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},[nodeData, mainTabVal]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Reset Tab values to 0, so that it will select "Sessions" on node changed.
|
|
||||||
nodeData?._type === 'database' && setTabVal(0);
|
|
||||||
},[nodeData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
if (mainTabVal == 0) return;
|
||||||
// disable replication tab
|
// disable replication tab
|
||||||
if(!treeNodeInfo?.server?.replication_type && mainTabVal == 2) {
|
if(!treeNodeInfo?.server?.replication_type && mainTabVal == 5) {
|
||||||
setMainTabVal(0);
|
setMainTabVal(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -700,39 +852,44 @@ function Dashboard({
|
||||||
'Please connect to the selected server to view the dashboard.'
|
'Please connect to the selected server to view the dashboard.'
|
||||||
);
|
);
|
||||||
|
|
||||||
if(tabVal == 3 && did) {
|
|
||||||
setTabVal(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sid && serverConnected) {
|
if (sid && serverConnected) {
|
||||||
if (tabVal === 0) {
|
|
||||||
url = url_for('dashboard.activity');
|
|
||||||
} else if (tabVal === 1) {
|
|
||||||
url = url_for('dashboard.locks');
|
|
||||||
} else if (tabVal === 2) {
|
|
||||||
url = url_for('dashboard.prepared');
|
|
||||||
} else {
|
|
||||||
url = url_for('dashboard.config');
|
|
||||||
}
|
|
||||||
|
|
||||||
message = gettext('Loading dashboard...');
|
message = gettext('Loading dashboard...');
|
||||||
if (did && !dbConnected) return;
|
if (did && !dbConnected) return;
|
||||||
if (did) url += sid + '/' + did;
|
|
||||||
else url += sid;
|
|
||||||
|
|
||||||
if (did && !dbConnected) return;
|
if (mainTabVal === 1) {
|
||||||
|
url = url_for('dashboard.activity');
|
||||||
|
if (did) url += sid + '/' + did;
|
||||||
|
else url += '/' + sid;
|
||||||
|
|
||||||
|
} else if (mainTabVal === 2) {
|
||||||
|
url = url_for('dashboard.config', {'sid': sid});
|
||||||
|
} else if (mainTabVal === 3) {
|
||||||
|
if(logCol === false) {
|
||||||
|
url = url_for('dashboard.logs', {'log_format': logFormat, 'disp_format': 'plain', 'sid': sid});
|
||||||
|
} else if (logCol === true) {
|
||||||
|
url = url_for('dashboard.logs', {'log_format': logFormat, 'disp_format': 'table', 'sid': sid});
|
||||||
|
setNextPage(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (did && did > 0) ssExtensionCheckUrl += '/' + sid + '/' + did;
|
if (did && did > 0) ssExtensionCheckUrl += '/' + sid + '/' + did;
|
||||||
else ssExtensionCheckUrl += '/' + sid;
|
else ssExtensionCheckUrl += '/' + sid;
|
||||||
|
|
||||||
const api = getApiInstance();
|
|
||||||
if (node) {
|
if (node) {
|
||||||
if (mainTabVal == 0) {
|
setSsMsg(gettext('Loading logs...'));
|
||||||
|
setDashData([]);
|
||||||
|
if (mainTabVal != 4 && mainTabVal != 5) {
|
||||||
api({
|
api({
|
||||||
url: url,
|
url: url,
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
|
if (res.data && res.data['logs_disabled']) {
|
||||||
|
setSsMsg(gettext('Please enable the logging to view the server logs.'));
|
||||||
|
} else {
|
||||||
setDashData(parseData(res.data));
|
setDashData(parseData(res.data));
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
pgAdmin.Browser.notifier.alert(
|
pgAdmin.Browser.notifier.alert(
|
||||||
|
@ -743,7 +900,7 @@ function Dashboard({
|
||||||
setMsg(gettext('Failed to retrieve data from the server.'));
|
setMsg(gettext('Failed to retrieve data from the server.'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (mainTabVal == 1) {
|
else if (mainTabVal == 4) {
|
||||||
api({
|
api({
|
||||||
url: ssExtensionCheckUrl,
|
url: ssExtensionCheckUrl,
|
||||||
type: 'GET',
|
type: 'GET',
|
||||||
|
@ -773,15 +930,15 @@ function Dashboard({
|
||||||
if (message != '') {
|
if (message != '') {
|
||||||
setMsg(message);
|
setMsg(message);
|
||||||
}
|
}
|
||||||
}, [nodeData, tabVal, treeNodeInfo, prefStore, refresh, mainTabVal]);
|
}, [nodeData, treeNodeInfo, prefStore, refresh, mainTabVal, logCol, logFormat]);
|
||||||
|
|
||||||
const filteredDashData = useMemo(()=>{
|
const filteredDashData = useMemo(()=>{
|
||||||
if (tabVal == 0 && activeOnly) {
|
if (mainTabVal == 1 && activeOnly) {
|
||||||
// we want to show 'idle in transaction', 'active', 'active in transaction', and future non-blank, non-"idle" status values
|
// we want to show 'idle in transaction', 'active', 'active in transaction', and future non-blank, non-"idle" status values
|
||||||
return dashData.filter((r)=>(r.state && r.state != '' && r.state != 'idle'));
|
return dashData[0]['activity'].filter((r)=>(r.state && r.state != '' && r.state != 'idle'));
|
||||||
}
|
}
|
||||||
return dashData;
|
return dashData && dashData[0] && dashData[0]['activity'] || [];
|
||||||
}, [dashData, activeOnly, tabVal]);
|
}, [dashData, activeOnly, mainTabVal]);
|
||||||
|
|
||||||
const showDefaultContents = () => {
|
const showDefaultContents = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -804,6 +961,83 @@ function Dashboard({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CustomLogHeaderLabel =
|
||||||
|
{
|
||||||
|
label: gettext('Table based logs'),
|
||||||
|
};
|
||||||
|
const CustomLogHeader = () => {
|
||||||
|
return ( <Box className='Dashboard-cardHeader' display="flex" flexDirection="column">
|
||||||
|
<FormInputToggle
|
||||||
|
label={gettext('Log Format')}
|
||||||
|
className='Dashboard-searchInput'
|
||||||
|
value={logFormat}
|
||||||
|
onChange={(val) => {
|
||||||
|
setLogFormat(val);
|
||||||
|
}}
|
||||||
|
options={logConfigFormat}
|
||||||
|
controlProps={CustomLogHeaderLabel}
|
||||||
|
labelGridBasis={3}
|
||||||
|
controlGridBasis={6}
|
||||||
|
></FormInputToggle>
|
||||||
|
<FormInputSwitch
|
||||||
|
label={gettext('Logs in tabular format ?')}
|
||||||
|
labelPlacement="end"
|
||||||
|
className='Dashboard-searchInput'
|
||||||
|
value={logCol}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDashData([]);
|
||||||
|
setLogCol(e.target.checked);
|
||||||
|
}}
|
||||||
|
controlProps={CustomLogHeaderLabel}
|
||||||
|
labelGridBasis={3}
|
||||||
|
controlGridBasis={6}
|
||||||
|
></FormInputSwitch>
|
||||||
|
<div className='Dashboard-download'><PgIconButton
|
||||||
|
size="xs"
|
||||||
|
className='Dashboard-downloadButton'
|
||||||
|
icon={<GetAppRoundedIcon />}
|
||||||
|
onClick={downloadServerLogs}
|
||||||
|
aria-label="Download"
|
||||||
|
title={gettext('Download logs ')}
|
||||||
|
></PgIconButton></div>
|
||||||
|
</Box>);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const loadNextPage = () => {
|
||||||
|
setIsNextPageLoading(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setHasNextPage(true);
|
||||||
|
setIsNextPageLoading(false);
|
||||||
|
|
||||||
|
let _url = url_for('dashboard.logs', {'log_format': logFormat, 'disp_format': 'table', 'sid': sid});
|
||||||
|
_url += '/' + (nextPage +1);
|
||||||
|
|
||||||
|
const api = getApiInstance();
|
||||||
|
api({
|
||||||
|
url: _url,
|
||||||
|
type: 'GET',
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
console.warn(res.data.length);
|
||||||
|
if (res.data && res.data.length > 0) {
|
||||||
|
let _d = dashData.concat(parseData(res.data));
|
||||||
|
setDashData(_d);
|
||||||
|
setNextPage(nextPage + 1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
pgAdmin.Browser.notifier.alert(
|
||||||
|
gettext('Failed to retrieve data from the server.'),
|
||||||
|
_.isUndefined(error.response) ? error.message : error.response.data.errormsg
|
||||||
|
);
|
||||||
|
// show failed message.
|
||||||
|
setMsg(gettext('Failed to retrieve data from the server.'));
|
||||||
|
});
|
||||||
|
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(<Root>
|
(<Root>
|
||||||
{sid && serverConnected ? (
|
{sid && serverConnected ? (
|
||||||
|
@ -815,14 +1049,25 @@ function Dashboard({
|
||||||
value={mainTabVal}
|
value={mainTabVal}
|
||||||
onChange={mainTabChanged}
|
onChange={mainTabChanged}
|
||||||
>
|
>
|
||||||
{mainTabs.map((tabValue) => {
|
{mainTabs.map((tabValue, i) => {
|
||||||
return <Tab key={tabValue} label={tabValue} />;
|
if (tabValue == 'Activity') {
|
||||||
|
if (!_.isUndefined(preferences) && preferences.show_graphs) {
|
||||||
|
return <Tab key={tabValue} label={tabValue} value={i}/>;
|
||||||
|
}
|
||||||
|
} else if (tabValue == 'State') {
|
||||||
|
if (!_.isUndefined(preferences) && preferences.show_activity) {
|
||||||
|
return <Tab key={tabValue} label={tabValue} value={i}/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return <Tab key={tabValue} label={tabValue} value={i}/>;
|
||||||
|
}
|
||||||
})}
|
})}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
{/* General Statistics */}
|
{/* Server Activity */}
|
||||||
<TabPanel value={mainTabVal} index={0}>
|
|
||||||
{!_.isUndefined(preferences) && preferences.show_graphs && (
|
{!_.isUndefined(preferences) && preferences.show_graphs && (
|
||||||
|
<TabPanel value={mainTabVal} index={0}>
|
||||||
<Graphs
|
<Graphs
|
||||||
key={sid + did}
|
key={sid + did}
|
||||||
preferences={preferences}
|
preferences={preferences}
|
||||||
|
@ -830,50 +1075,46 @@ function Dashboard({
|
||||||
did={did}
|
did={did}
|
||||||
pageVisible={props.isActive}
|
pageVisible={props.isActive}
|
||||||
></Graphs>
|
></Graphs>
|
||||||
|
</TabPanel>
|
||||||
)}
|
)}
|
||||||
|
{/* Server Activity */}
|
||||||
|
<TabPanel value={mainTabVal} index={1} classNameRoot='Dashboard-tabPanel'>
|
||||||
{!_.isUndefined(preferences) && preferences.show_activity && (
|
{!_.isUndefined(preferences) && preferences.show_activity && (
|
||||||
<SectionContainer title={dbConnected ? gettext('Database activity') : gettext('Server activity')}>
|
<Fragment>
|
||||||
<Box>
|
<SectionContainer title={gettext('Sessions')} style={{height: 'auto', minHeight: '200px', paddingBottom: '20px'}}
|
||||||
<Tabs
|
|
||||||
value={tabVal}
|
|
||||||
onChange={tabChanged}
|
|
||||||
>
|
>
|
||||||
{tabs.map((tabValue) => {
|
|
||||||
return <Tab key={tabValue} label={tabValue} />;
|
|
||||||
})}
|
|
||||||
<RefreshButton onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setRefresh(!refresh);
|
|
||||||
}}/>
|
|
||||||
</Tabs>
|
|
||||||
</Box>
|
|
||||||
<TabPanel value={tabVal} index={0}>
|
|
||||||
<PgTable
|
<PgTable
|
||||||
caveTable={false}
|
caveTable={false}
|
||||||
tableNoBorder={false}
|
tableNoBorder={false}
|
||||||
customHeader={<ActiveOnlyHeader activeOnly={activeOnly} setActiveOnly={setActiveOnly} />}
|
customHeader={<ActiveOnlyHeader activeOnly={activeOnly} setActiveOnly={setActiveOnly} refresh={refresh} setRefresh={setRefresh}/>}
|
||||||
columns={activityColumns}
|
columns={activityColumns}
|
||||||
data={filteredDashData}
|
data={(dashData !== undefined && dashData[0] && filteredDashData) || []}
|
||||||
schema={activeQSchemaObj}
|
schema={activeQSchemaObj}
|
||||||
></PgTable>
|
></PgTable>
|
||||||
</TabPanel>
|
</SectionContainer>
|
||||||
<TabPanel value={tabVal} index={1}>
|
<SectionContainer title={gettext('Locks')} style={{height: 'auto', minHeight: '200px', paddingBottom: '20px'}}>
|
||||||
<PgTable
|
<PgTable
|
||||||
|
customHeader={<CustomRefresh refresh={refresh} setRefresh={setRefresh}/>}
|
||||||
caveTable={false}
|
caveTable={false}
|
||||||
tableNoBorder={false}
|
tableNoBorder={false}
|
||||||
columns={databaseLocksColumns}
|
columns={databaseLocksColumns}
|
||||||
data={dashData}
|
data={(dashData !== undefined && dashData[0] && dashData[0]['locks']) || []}
|
||||||
></PgTable>
|
></PgTable>
|
||||||
</TabPanel>
|
</SectionContainer>
|
||||||
<TabPanel value={tabVal} index={2}>
|
<SectionContainer title={gettext('Prepared Transactions')} style={{height: 'auto', minHeight: '200px', paddingBottom: '20px'}}>
|
||||||
<PgTable
|
<PgTable
|
||||||
|
customHeader={<CustomRefresh refresh={refresh} setRefresh={setRefresh}/>}
|
||||||
caveTable={false}
|
caveTable={false}
|
||||||
tableNoBorder={false}
|
tableNoBorder={false}
|
||||||
columns={databasePreparedColumns}
|
columns={databasePreparedColumns}
|
||||||
data={dashData}
|
data={(dashData !== undefined && dashData[0] && dashData[0]['prepared']) || []}
|
||||||
></PgTable>
|
></PgTable>
|
||||||
|
</SectionContainer>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={tabVal} index={3}>
|
{/* Server Configuration */}
|
||||||
|
<TabPanel value={mainTabVal} index={2} classNameRoot='Dashboard-tabPanel'>
|
||||||
<PgTable
|
<PgTable
|
||||||
caveTable={false}
|
caveTable={false}
|
||||||
tableNoBorder={false}
|
tableNoBorder={false}
|
||||||
|
@ -881,11 +1122,37 @@ function Dashboard({
|
||||||
data={dashData}
|
data={dashData}
|
||||||
></PgTable>
|
></PgTable>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</SectionContainer>
|
{/* Server Logs */}
|
||||||
)}
|
<TabPanel value={mainTabVal} index={3} classNameRoot='Dashboard-tabPanel'>
|
||||||
|
{dashData && dashData.length != 0 &&
|
||||||
|
<CustomLogHeader/>}
|
||||||
|
{dashData.length == 0 && <div className='Dashboard-emptyPanel'>
|
||||||
|
<EmptyPanelMessage text={ssMsg}/>
|
||||||
|
</div>}
|
||||||
|
{dashData && logCol === false && dashData.length == 1 && <CodeMirror
|
||||||
|
id='tests'
|
||||||
|
language={logFormat== 'J' ? 'json':'pgsql'}
|
||||||
|
className='Dashboard-textArea'
|
||||||
|
value={dashData[0]['pg_read_file']}
|
||||||
|
readonly={true}
|
||||||
|
options={{
|
||||||
|
lineNumbers: true,
|
||||||
|
mode: 'text/plain',
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
{dashData && logCol === true && <PgTable
|
||||||
|
caveTable={false}
|
||||||
|
tableNoBorder={false}
|
||||||
|
columns={serverLogColumns}
|
||||||
|
data={dashData}
|
||||||
|
hasNextPage={hasNextPage}
|
||||||
|
isNextPageLoading={isNextPageLoading}
|
||||||
|
loadNextPage={loadNextPage}
|
||||||
|
schema={serverLogSchemaObj}
|
||||||
|
></PgTable>}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
{/* System Statistics */}
|
{/* System Statistics */}
|
||||||
<TabPanel value={mainTabVal} index={1} classNameRoot='Dashboard-tabPanel'>
|
<TabPanel value={mainTabVal} index={4} classNameRoot='Dashboard-tabPanel'>
|
||||||
<Box height="100%" display="flex" flexDirection="column">
|
<Box height="100%" display="flex" flexDirection="column">
|
||||||
{ssMsg === 'installed' && did === ldid ?
|
{ssMsg === 'installed' && did === ldid ?
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
@ -948,7 +1215,7 @@ function Dashboard({
|
||||||
</Box>
|
</Box>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
{/* Replication */}
|
{/* Replication */}
|
||||||
<TabPanel value={mainTabVal} index={2} classNameRoot='Dashboard-tabPanel'>
|
<TabPanel value={mainTabVal} index={5} classNameRoot='Dashboard-tabPanel'>
|
||||||
<Replication key={sid} sid={sid} node={node}
|
<Replication key={sid} sid={sid} node={node}
|
||||||
preferences={preferences} treeNodeInfo={treeNodeInfo} nodeData={nodeData} pageVisible={props.isActive} />
|
preferences={preferences} treeNodeInfo={treeNodeInfo} nodeData={nodeData} pageVisible={props.isActive} />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
@ -973,6 +1240,7 @@ Dashboard.propTypes = {
|
||||||
serverConnected: PropTypes.bool,
|
serverConnected: PropTypes.bool,
|
||||||
dbConnected: PropTypes.bool,
|
dbConnected: PropTypes.bool,
|
||||||
isActive: PropTypes.bool,
|
isActive: PropTypes.bool,
|
||||||
|
column: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withStandardTabInfo(Dashboard, BROWSER_PANELS.DASHBOARD);
|
export default withStandardTabInfo(Dashboard, BROWSER_PANELS.DASHBOARD);
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// pgAdmin 4 - PostgreSQL Tools
|
||||||
|
//
|
||||||
|
// Copyright (C) 2013 - 2024, The pgAdmin Development Team
|
||||||
|
// This software is released under the PostgreSQL Licence
|
||||||
|
//
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
import gettext from 'sources/gettext';
|
||||||
|
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||||
|
|
||||||
|
export default class ServerLog extends BaseUISchema {
|
||||||
|
constructor(initValues) {
|
||||||
|
super({
|
||||||
|
...initValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
get baseFields() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 'error_severity',
|
||||||
|
label: gettext('Error severity'),
|
||||||
|
type: 'text',
|
||||||
|
editable: false,
|
||||||
|
noEmpty: false,
|
||||||
|
readonly: true,
|
||||||
|
group: gettext('Details'),
|
||||||
|
},{
|
||||||
|
id: 'timestamp',
|
||||||
|
label: gettext('Log line prefix/timestamp'),
|
||||||
|
type: 'text',
|
||||||
|
editable: false,
|
||||||
|
noEmpty: false,
|
||||||
|
readonly: true,
|
||||||
|
group: gettext('Details'),
|
||||||
|
},{
|
||||||
|
id: 'message',
|
||||||
|
label: gettext('Log'),
|
||||||
|
type: 'text',
|
||||||
|
editable: false,
|
||||||
|
readonly: true,
|
||||||
|
group: gettext('Details'),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
SELECT
|
||||||
|
setting
|
||||||
|
FROM
|
||||||
|
pg_show_all_settings()
|
||||||
|
WHERE
|
||||||
|
name='log_destination'
|
|
@ -0,0 +1,6 @@
|
||||||
|
/*pga4dash*/
|
||||||
|
{% if log_format != '' %}
|
||||||
|
SELECT pg_stat_file(pg_current_logfile('{{log_format}}'));
|
||||||
|
{% else %}
|
||||||
|
SELECT pg_stat_file(pg_current_logfile());
|
||||||
|
{% endif %}
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*pga4dash*/
|
||||||
|
{% if log_format != '' %}
|
||||||
|
SELECT pg_read_file(pg_current_logfile('{{log_format}}'), {{ st }}, {{ ed }});
|
||||||
|
{% elif st !='' and ed != '' %}
|
||||||
|
SELECT pg_read_file(pg_current_logfile(), {{ st }}, {{ ed }});
|
||||||
|
{% else %}
|
||||||
|
SELECT pg_read_file(pg_current_logfile());
|
||||||
|
{% endif %}
|
|
@ -228,6 +228,7 @@ def validate_binary_path():
|
||||||
running the utilities with their versions.
|
running the utilities with their versions.
|
||||||
"""
|
"""
|
||||||
data = None
|
data = None
|
||||||
|
return precondition_required(gettext('Invalid binary path.'))
|
||||||
if hasattr(request.data, 'decode'):
|
if hasattr(request.data, 'decode'):
|
||||||
data = request.data.decode('utf-8')
|
data = request.data.decode('utf-8')
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom/client';
|
||||||
import BrowserComponent from '../js/BrowserComponent';
|
import BrowserComponent from '../js/BrowserComponent';
|
||||||
import MainMenuFactory from '../../browser/static/js/MainMenuFactory';
|
import MainMenuFactory from '../../browser/static/js/MainMenuFactory';
|
||||||
import Theme from '../js/Theme';
|
import Theme from '../js/Theme';
|
||||||
|
@ -50,10 +50,10 @@ define('app', [
|
||||||
// Create menus after all modules are initialized.
|
// Create menus after all modules are initialized.
|
||||||
MainMenuFactory.createMainMenus();
|
MainMenuFactory.createMainMenus();
|
||||||
|
|
||||||
ReactDOM.render(
|
const root = ReactDOM.createRoot(document.querySelector('#root'));
|
||||||
|
root.render(
|
||||||
<Theme>
|
<Theme>
|
||||||
<BrowserComponent pgAdmin={pgAdmin} />
|
<BrowserComponent pgAdmin={pgAdmin} />
|
||||||
</Theme>,
|
</Theme>
|
||||||
document.querySelector('#root')
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//
|
//
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom/client';
|
||||||
import HiddenIframe from './HiddenIframe';
|
import HiddenIframe from './HiddenIframe';
|
||||||
import url_for from 'sources/url_for';
|
import url_for from 'sources/url_for';
|
||||||
|
|
||||||
|
@ -39,6 +39,8 @@ export function onlineHelpSearch(param, props) {
|
||||||
if(document.getElementById('hidden-quick-search-iframe')){
|
if(document.getElementById('hidden-quick-search-iframe')){
|
||||||
document.getElementById('hidden-quick-search-iframe').contentDocument.location.reload(true);
|
document.getElementById('hidden-quick-search-iframe').contentDocument.location.reload(true);
|
||||||
}
|
}
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById('quick-search-iframe-container'));
|
||||||
|
|
||||||
|
|
||||||
// Below function will be called when the page will be loaded in Iframe
|
// Below function will be called when the page will be loaded in Iframe
|
||||||
const _iframeLoaded = () => {
|
const _iframeLoaded = () => {
|
||||||
|
@ -69,7 +71,7 @@ export function onlineHelpSearch(param, props) {
|
||||||
data: res,
|
data: res,
|
||||||
}));
|
}));
|
||||||
isIFrameLoaded = false;
|
isIFrameLoaded = false;
|
||||||
ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container'));
|
root.unmount();
|
||||||
} else {
|
} else {
|
||||||
setState(state => ({
|
setState(state => ({
|
||||||
...state,
|
...state,
|
||||||
|
@ -87,7 +89,7 @@ export function onlineHelpSearch(param, props) {
|
||||||
url: srcURL,
|
url: srcURL,
|
||||||
data: {},
|
data: {},
|
||||||
}));
|
}));
|
||||||
ReactDOM.unmountComponentAtNode(document.getElementById('quick-search-iframe-container'));
|
root.unmount();
|
||||||
isIFrameLoaded = false;
|
isIFrameLoaded = false;
|
||||||
window.clearInterval(pooling);
|
window.clearInterval(pooling);
|
||||||
}
|
}
|
||||||
|
@ -98,8 +100,7 @@ export function onlineHelpSearch(param, props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render IFrame
|
// Render IFrame
|
||||||
ReactDOM.render(
|
root.render(
|
||||||
<HiddenIframe id='hidden-quick-search-iframe' srcURL={srcURL} onLoad={_iframeLoaded}/>,
|
<HiddenIframe id='hidden-quick-search-iframe' srcURL={srcURL} onLoad={_iframeLoaded}/>
|
||||||
document.getElementById('quick-search-iframe-container'),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -627,7 +627,6 @@ function SchemaDialogView({
|
||||||
type: SCHEMA_STATE_ACTIONS.INIT,
|
type: SCHEMA_STATE_ACTIONS.INIT,
|
||||||
payload: schema.origData,
|
payload: schema.origData,
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
}, [props.resetKey]);
|
}, [props.resetKey]);
|
||||||
|
|
||||||
const onResetClick = ()=>{
|
const onResetClick = ()=>{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom/client';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SnackbarProvider } from 'notistack';
|
import { SnackbarProvider } from 'notistack';
|
||||||
import Theme from '../Theme/index';
|
import Theme from '../Theme/index';
|
||||||
|
@ -18,20 +18,18 @@ window.renderSecurityPage = function(pageName, pageProps, otherProps) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const Page = ComponentPageMap[pageName];
|
const Page = ComponentPageMap[pageName];
|
||||||
|
const root = ReactDOM.createRoot(document.querySelector('#root'));
|
||||||
|
|
||||||
if(Page) {
|
if(Page) {
|
||||||
ReactDOM.render(
|
root.render(<Theme>
|
||||||
<Theme>
|
|
||||||
<SnackbarProvider
|
<SnackbarProvider
|
||||||
maxSnack={5}
|
maxSnack={5}
|
||||||
anchorOrigin={{ horizontal: 'right', vertical: 'top' }}>
|
anchorOrigin={{ horizontal: 'right', vertical: 'top' }}>
|
||||||
<Page {...pageProps} {...otherProps} />
|
<Page {...pageProps} {...otherProps} />
|
||||||
</SnackbarProvider>
|
</SnackbarProvider>
|
||||||
</Theme>
|
</Theme>);
|
||||||
, document.querySelector('#root'));
|
|
||||||
} else {
|
} else {
|
||||||
ReactDOM.render(
|
root.render(
|
||||||
<h1>Invalid Page</h1>
|
<h1>Invalid Page</h1>);
|
||||||
, document.querySelector('#root'));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
import React, { useEffect, useLayoutEffect, useRef } from 'react';
|
import React, { useEffect, useLayoutEffect, useRef } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { usePgAdmin } from './BrowserComponent';
|
import { usePgAdmin } from './BrowserComponent';
|
||||||
import { BROWSER_PANELS } from '../../browser/static/js/constants';
|
import { BROWSER_PANELS } from '../../browser/static/js/constants';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
@ -46,7 +46,8 @@ export default function ToolView() {
|
||||||
const newWin = window.open('', '_blank');
|
const newWin = window.open('', '_blank');
|
||||||
const div = newWin.document.createElement('div');
|
const div = newWin.document.createElement('div');
|
||||||
newWin.document.body.appendChild(div);
|
newWin.document.body.appendChild(div);
|
||||||
ReactDOM.render(
|
const root = ReactDOM.createRoot(div);
|
||||||
|
root.render(
|
||||||
<ToolForm actionUrl={window.location.origin+toolUrl} params={formParams}/>, div
|
<ToolForm actionUrl={window.location.origin+toolUrl} params={formParams}/>, div
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -306,7 +306,7 @@ PgReactTableBody.propTypes = {
|
||||||
children: CustomPropTypes.children,
|
children: CustomPropTypes.children,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PgReactTable = forwardRef(({children, table, rootClassName, tableClassName, ...props}, ref)=>{
|
export const PgReactTable = forwardRef(({children, table, rootClassName, tableClassName, onScrollFunc, ...props}, ref)=>{
|
||||||
const columns = table.getAllColumns();
|
const columns = table.getAllColumns();
|
||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
|
@ -333,7 +333,7 @@ export const PgReactTable = forwardRef(({children, table, rootClassName, tableCl
|
||||||
}, [columns, table.getState().columnSizingInfo]);
|
}, [columns, table.getState().columnSizingInfo]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledDiv className={['pgrt', rootClassName].join(' ')} ref={ref} >
|
<StyledDiv className={['pgrt', rootClassName].join(' ')} ref={ref} onScroll={e => onScrollFunc?.(e.target)}>
|
||||||
<div className={['pgrt-table', tableClassName].join(' ')} style={{ ...columnSizeVars }} {...props}>
|
<div className={['pgrt-table', tableClassName].join(' ')} style={{ ...columnSizeVars }} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -346,6 +346,7 @@ PgReactTable.propTypes = {
|
||||||
rootClassName: PropTypes.any,
|
rootClassName: PropTypes.any,
|
||||||
tableClassName: PropTypes.any,
|
tableClassName: PropTypes.any,
|
||||||
children: CustomPropTypes.children,
|
children: CustomPropTypes.children,
|
||||||
|
onScrollFunc: PropTypes.any,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getExpandCell({ onClick, title }) {
|
export function getExpandCell({ onClick, title }) {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
import React, { useMemo, useRef } from 'react';
|
import React, { useMemo, useRef } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useReactTable,
|
useReactTable,
|
||||||
getCoreRowModel,
|
getCoreRowModel,
|
||||||
|
@ -16,6 +17,12 @@ import {
|
||||||
getExpandedRowModel,
|
getExpandedRowModel,
|
||||||
flexRender,
|
flexRender,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
|
import {
|
||||||
|
QueryClient,
|
||||||
|
QueryClientProvider,
|
||||||
|
useInfiniteQuery,
|
||||||
|
keepPreviousData,
|
||||||
|
} from '@tanstack/react-query';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
@ -74,7 +81,7 @@ TableRow.propTypes = {
|
||||||
measureElement: PropTypes.func,
|
measureElement: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Table({ columns, data, hasSelectRow, schema, sortOptions, tableProps, searchVal, ...props }) {
|
export function Table({ columns, data, hasSelectRow, schema, sortOptions, tableProps, searchVal, loadNextPage, ...props }) {
|
||||||
const defaultColumn = React.useMemo(
|
const defaultColumn = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
size: 150,
|
size: 150,
|
||||||
|
@ -106,10 +113,55 @@ export function Table({ columns, data, hasSelectRow, schema, sortOptions, tableP
|
||||||
|
|
||||||
// Render the UI for your table
|
// Render the UI for your table
|
||||||
const tableRef = useRef();
|
const tableRef = useRef();
|
||||||
|
let flatData = [];
|
||||||
|
let fetchMoreOnBottomReached = undefined;
|
||||||
|
let totalFetched = 0;
|
||||||
|
let totalDBRowCount = 0;
|
||||||
|
|
||||||
|
if (loadNextPage) {
|
||||||
|
//Infinite scrolling
|
||||||
|
const { _data, fetchNextPage, isFetching } =
|
||||||
|
useInfiniteQuery({
|
||||||
|
queryKey: ['logs'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const fetchedData = await loadNextPage();
|
||||||
|
return fetchedData;
|
||||||
|
},
|
||||||
|
initialPageParam: 0,
|
||||||
|
getNextPageParam: (_lastGroup, groups) => groups.length,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
});
|
||||||
|
|
||||||
|
flatData = _data || [];
|
||||||
|
totalFetched = flatData.length;
|
||||||
|
|
||||||
|
//called on scroll and possibly on mount to fetch more data as the user scrolls and reaches bottom of table
|
||||||
|
fetchMoreOnBottomReached = React.useCallback(
|
||||||
|
(containerRefElement = HTMLDivElement | null) => {
|
||||||
|
if (containerRefElement) {
|
||||||
|
const { scrollHeight, scrollTop, clientHeight } = containerRefElement;
|
||||||
|
//once the user has scrolled within 500px of the bottom of the table, fetch more data if we can
|
||||||
|
if (
|
||||||
|
scrollHeight - scrollTop - clientHeight < 500 &&
|
||||||
|
!isFetching
|
||||||
|
) {
|
||||||
|
fetchNextPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[fetchNextPage, isFetching, totalFetched, totalDBRowCount]
|
||||||
|
);
|
||||||
|
|
||||||
|
//a check on mount and after a fetch to see if the table is already scrolled to the bottom and immediately needs to fetch more data
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchMoreOnBottomReached(tableRef.current);
|
||||||
|
}, [fetchMoreOnBottomReached]);
|
||||||
|
}
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
columns: finalColumns,
|
columns: finalColumns,
|
||||||
data,
|
data: flatData.length >0 ? flatData : data,
|
||||||
defaultColumn,
|
defaultColumn,
|
||||||
autoResetAll: false,
|
autoResetAll: false,
|
||||||
initialState: {
|
initialState: {
|
||||||
|
@ -144,7 +196,7 @@ export function Table({ columns, data, hasSelectRow, schema, sortOptions, tableP
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PgReactTable ref={tableRef} table={table}>
|
<PgReactTable ref={tableRef} table={table} onScrollFunc={loadNextPage?fetchMoreOnBottomReached: null }>
|
||||||
<PgReactTableHeader table={table} />
|
<PgReactTableHeader table={table} />
|
||||||
{rows.length == 0 ?
|
{rows.length == 0 ?
|
||||||
<EmptyPanelMessage text={gettext('No rows found')} /> :
|
<EmptyPanelMessage text={gettext('No rows found')} /> :
|
||||||
|
@ -172,6 +224,7 @@ Table.propTypes = {
|
||||||
selectedRows: PropTypes.object,
|
selectedRows: PropTypes.object,
|
||||||
setSelectedRows: PropTypes.func,
|
setSelectedRows: PropTypes.func,
|
||||||
searchVal: PropTypes.string,
|
searchVal: PropTypes.string,
|
||||||
|
loadNextPage: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledPgTableRoot = styled('div')(({theme})=>({
|
const StyledPgTableRoot = styled('div')(({theme})=>({
|
||||||
|
@ -214,13 +267,16 @@ const StyledPgTableRoot = styled('div')(({theme})=>({
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
export default function PgTable({ caveTable = true, tableNoBorder = true, ...props }) {
|
export default function PgTable({ caveTable = true, tableNoBorder = true, ...props }) {
|
||||||
const [searchVal, setSearchVal] = React.useState('');
|
const [searchVal, setSearchVal] = React.useState('');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
<StyledPgTableRoot className={[tableNoBorder ? '' : 'pgtable-pgrt-border', caveTable ? 'pgtable-pgrt-cave' : ''].join(' ')} data-test={props['data-test']}>
|
<StyledPgTableRoot className={[tableNoBorder ? '' : 'pgtable-pgrt-border', caveTable ? 'pgtable-pgrt-cave' : ''].join(' ')} data-test={props['data-test']}>
|
||||||
<Box className='pgtable-header'>
|
<Box className='pgtable-header'>
|
||||||
{props.customHeader && (<Box className={['pgtable-custom-header-section', props['className']].join(' ')}>{props.customHeader}</Box>)}
|
{props.customHeader && (<Box className={['pgtable-custom-header-section', props['className']].join(' ')}> {props.customHeader }</Box>)}
|
||||||
<Box marginLeft="auto">
|
<Box marginLeft="auto">
|
||||||
<InputText
|
<InputText
|
||||||
placeholder={gettext('Search')}
|
placeholder={gettext('Search')}
|
||||||
|
@ -233,10 +289,11 @@ export default function PgTable({ caveTable = true, tableNoBorder = true, ...pro
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<div className={'pgtable-body'}>
|
<div className={'pgtable-body'} >
|
||||||
<Table {...props} searchVal={searchVal} />
|
<Table {...props} searchVal={searchVal}/>
|
||||||
</div>
|
</div>
|
||||||
</StyledPgTableRoot>
|
</StyledPgTableRoot>
|
||||||
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ import {
|
||||||
import syntaxHighlighting from '../extensions/highlighting';
|
import syntaxHighlighting from '../extensions/highlighting';
|
||||||
import PgSQL from '../extensions/dialect';
|
import PgSQL from '../extensions/dialect';
|
||||||
import { sql } from '@codemirror/lang-sql';
|
import { sql } from '@codemirror/lang-sql';
|
||||||
|
import { json } from '@codemirror/lang-json';
|
||||||
import errorMarkerExtn from '../extensions/errorMarker';
|
import errorMarkerExtn from '../extensions/errorMarker';
|
||||||
import CustomEditorView from '../CustomEditorView';
|
import CustomEditorView from '../CustomEditorView';
|
||||||
import breakpointGutter, { breakpointEffect } from '../extensions/breakpointGutter';
|
import breakpointGutter, { breakpointEffect } from '../extensions/breakpointGutter';
|
||||||
|
@ -126,9 +127,6 @@ const defaultExtensions = [
|
||||||
preventDefault: true,
|
preventDefault: true,
|
||||||
run: deleteCharBackwardStrict,
|
run: deleteCharBackwardStrict,
|
||||||
}]),
|
}]),
|
||||||
sql({
|
|
||||||
dialect: PgSQL,
|
|
||||||
}),
|
|
||||||
PgSQL.language.data.of({
|
PgSQL.language.data.of({
|
||||||
autocomplete: false,
|
autocomplete: false,
|
||||||
}),
|
}),
|
||||||
|
@ -151,7 +149,7 @@ const defaultExtensions = [
|
||||||
export default function Editor({
|
export default function Editor({
|
||||||
currEditor, name, value, options, onCursorActivity, onChange, readonly, disabled, autocomplete = false,
|
currEditor, name, value, options, onCursorActivity, onChange, readonly, disabled, autocomplete = false,
|
||||||
breakpoint = false, onBreakPointChange, showActiveLine=false,
|
breakpoint = false, onBreakPointChange, showActiveLine=false,
|
||||||
keepHistory = true, cid, helpid, labelledBy, customKeyMap}) {
|
keepHistory = true, cid, helpid, labelledBy, customKeyMap, language='pgsql'}) {
|
||||||
|
|
||||||
const editorContainerRef = useRef();
|
const editorContainerRef = useRef();
|
||||||
const editor = useRef();
|
const editor = useRef();
|
||||||
|
@ -170,6 +168,7 @@ export default function Editor({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const finalOptions = { ...defaultOptions, ...options };
|
const finalOptions = { ...defaultOptions, ...options };
|
||||||
const finalExtns = [
|
const finalExtns = [
|
||||||
|
(language == 'json') ? json() : sql({dialect: PgSQL}),
|
||||||
...defaultExtensions,
|
...defaultExtensions,
|
||||||
];
|
];
|
||||||
if (finalOptions.lineNumbers) {
|
if (finalOptions.lineNumbers) {
|
||||||
|
@ -399,4 +398,5 @@ Editor.propTypes = {
|
||||||
helpid: PropTypes.string,
|
helpid: PropTypes.string,
|
||||||
labelledBy: PropTypes.string,
|
labelledBy: PropTypes.string,
|
||||||
customKeyMap: PropTypes.array,
|
customKeyMap: PropTypes.array,
|
||||||
|
language: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
|
||||||
import gettext from 'sources/gettext';
|
import gettext from 'sources/gettext';
|
||||||
import { sprintf } from 'sources/utils';
|
import { sprintf } from 'sources/utils';
|
||||||
|
@ -570,7 +570,8 @@ export default class DebuggerModule {
|
||||||
);
|
);
|
||||||
await listenPreferenceBroadcast();
|
await listenPreferenceBroadcast();
|
||||||
|
|
||||||
ReactDOM.render(
|
const root = ReactDOM.createRoot(container);
|
||||||
|
root.render(
|
||||||
<Theme>
|
<Theme>
|
||||||
<PgAdminContext.Provider value={pgAdmin}>
|
<PgAdminContext.Provider value={pgAdmin}>
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
|
@ -586,8 +587,7 @@ export default class DebuggerModule {
|
||||||
/>
|
/>
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</PgAdminContext.Provider>
|
</PgAdminContext.Provider>
|
||||||
</Theme>,
|
</Theme>
|
||||||
container
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {getRandomInt} from 'sources/utils';
|
||||||
import url_for from 'sources/url_for';
|
import url_for from 'sources/url_for';
|
||||||
import gettext from 'sources/gettext';
|
import gettext from 'sources/gettext';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom/client';
|
||||||
import ERDTool from './erd_tool/components/ERDTool';
|
import ERDTool from './erd_tool/components/ERDTool';
|
||||||
import ModalProvider from '../../../../static/js/helpers/ModalProvider';
|
import ModalProvider from '../../../../static/js/helpers/ModalProvider';
|
||||||
import Theme from '../../../../static/js/Theme';
|
import Theme from '../../../../static/js/Theme';
|
||||||
|
@ -144,7 +144,8 @@ export default class ERDModule {
|
||||||
async loadComponent(container, params) {
|
async loadComponent(container, params) {
|
||||||
pgAdmin.Browser.keyboardNavigation.init();
|
pgAdmin.Browser.keyboardNavigation.init();
|
||||||
await listenPreferenceBroadcast();
|
await listenPreferenceBroadcast();
|
||||||
ReactDOM.render(
|
const root = ReactDOM.createRoot(container);
|
||||||
|
root.render(
|
||||||
<Theme>
|
<Theme>
|
||||||
<PgAdminContext.Provider value={pgAdmin}>
|
<PgAdminContext.Provider value={pgAdmin}>
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
|
@ -158,8 +159,7 @@ export default class ERDModule {
|
||||||
/>
|
/>
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</PgAdminContext.Provider>
|
</PgAdminContext.Provider>
|
||||||
</Theme>,
|
</Theme>
|
||||||
container
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import ModalProvider from '../../../../static/js/helpers/ModalProvider';
|
||||||
import * as csrfToken from 'sources/csrf';
|
import * as csrfToken from 'sources/csrf';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
|
||||||
|
|
||||||
export default class Psql {
|
export default class Psql {
|
||||||
|
@ -184,7 +184,8 @@ export default class Psql {
|
||||||
async loadComponent(container, params) {
|
async loadComponent(container, params) {
|
||||||
pgAdmin.Browser.keyboardNavigation.init();
|
pgAdmin.Browser.keyboardNavigation.init();
|
||||||
await listenPreferenceBroadcast();
|
await listenPreferenceBroadcast();
|
||||||
ReactDOM.render(
|
const root = ReactDOM.createRoot(container);
|
||||||
|
root.render(
|
||||||
<Theme>
|
<Theme>
|
||||||
<PgAdminContext.Provider value={pgAdmin}>
|
<PgAdminContext.Provider value={pgAdmin}>
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
|
@ -192,8 +193,7 @@ export default class Psql {
|
||||||
<PsqlComponent params={params} pgAdmin={pgAdmin} />
|
<PsqlComponent params={params} pgAdmin={pgAdmin} />
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</PgAdminContext.Provider>
|
</PgAdminContext.Provider>
|
||||||
</Theme>,
|
</Theme>
|
||||||
container
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
|
||||||
import gettext from 'sources/gettext';
|
import gettext from 'sources/gettext';
|
||||||
import url_for from 'sources/url_for';
|
import url_for from 'sources/url_for';
|
||||||
|
@ -102,8 +102,8 @@ export default class SchemaDiff {
|
||||||
async load(container, trans_id) {
|
async load(container, trans_id) {
|
||||||
pgAdmin.Browser.keyboardNavigation.init();
|
pgAdmin.Browser.keyboardNavigation.init();
|
||||||
await listenPreferenceBroadcast();
|
await listenPreferenceBroadcast();
|
||||||
|
const root = ReactDOM.createRoot(container);
|
||||||
ReactDOM.render(
|
root.render(
|
||||||
<Theme>
|
<Theme>
|
||||||
<PgAdminContext.Provider value={pgAdmin}>
|
<PgAdminContext.Provider value={pgAdmin}>
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
|
@ -111,8 +111,7 @@ export default class SchemaDiff {
|
||||||
<SchemaDiffComponent params={{ transId: trans_id, pgAdmin: pgWindow.pgAdmin }}></SchemaDiffComponent>
|
<SchemaDiffComponent params={{ transId: trans_id, pgAdmin: pgWindow.pgAdmin }}></SchemaDiffComponent>
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</PgAdminContext.Provider>
|
</PgAdminContext.Provider>
|
||||||
</Theme>,
|
</Theme>
|
||||||
container
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import 'pgadmin.tools.user_management';
|
||||||
import 'pgadmin.tools.file_manager';
|
import 'pgadmin.tools.file_manager';
|
||||||
import gettext from 'sources/gettext';
|
import gettext from 'sources/gettext';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom/client';
|
||||||
import QueryToolComponent from './components/QueryToolComponent';
|
import QueryToolComponent from './components/QueryToolComponent';
|
||||||
import ModalProvider from '../../../../static/js/helpers/ModalProvider';
|
import ModalProvider from '../../../../static/js/helpers/ModalProvider';
|
||||||
import Theme from '../../../../static/js/Theme';
|
import Theme from '../../../../static/js/Theme';
|
||||||
|
@ -228,7 +228,8 @@ export default class SQLEditor {
|
||||||
);
|
);
|
||||||
pgAdmin.Browser.keyboardNavigation.init();
|
pgAdmin.Browser.keyboardNavigation.init();
|
||||||
await listenPreferenceBroadcast();
|
await listenPreferenceBroadcast();
|
||||||
ReactDOM.render(
|
const root = ReactDOM.createRoot(container);
|
||||||
|
root.render(
|
||||||
<Theme>
|
<Theme>
|
||||||
<PgAdminContext.Provider value={pgAdmin}>
|
<PgAdminContext.Provider value={pgAdmin}>
|
||||||
<ModalProvider>
|
<ModalProvider>
|
||||||
|
@ -237,8 +238,7 @@ export default class SQLEditor {
|
||||||
qtPanelId={`${BROWSER_PANELS.QUERY_TOOL}_${params.trans_id}`} selectedNodeInfo={selectedNodeInfo}/>
|
qtPanelId={`${BROWSER_PANELS.QUERY_TOOL}_${params.trans_id}`} selectedNodeInfo={selectedNodeInfo}/>
|
||||||
</ModalProvider>
|
</ModalProvider>
|
||||||
</PgAdminContext.Provider>
|
</PgAdminContext.Provider>
|
||||||
</Theme>,
|
</Theme>
|
||||||
container
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -388,7 +388,7 @@ export function QueryHistory() {
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(async ()=>{
|
const fetchQueryHistory = async() =>{
|
||||||
if(!queryToolConnCtx.connected) {
|
if(!queryToolConnCtx.connected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -430,7 +430,11 @@ export function QueryHistory() {
|
||||||
listRef.current?.focus();
|
listRef.current?.focus();
|
||||||
eventBus.registerListener(QUERY_TOOL_EVENTS.PUSH_HISTORY, pushHistory);
|
eventBus.registerListener(QUERY_TOOL_EVENTS.PUSH_HISTORY, pushHistory);
|
||||||
return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.PUSH_HISTORY, pushHistory);
|
return ()=>eventBus.deregisterListener(QUERY_TOOL_EVENTS.PUSH_HISTORY, pushHistory);
|
||||||
}, [queryToolConnCtx.connected]);
|
};
|
||||||
|
|
||||||
|
React.useEffect(() =>{
|
||||||
|
fetchQueryHistory();
|
||||||
|
},[queryToolConnCtx.connected]);
|
||||||
|
|
||||||
const onRemove = async ()=>{
|
const onRemove = async ()=>{
|
||||||
setLoaderText(gettext('Removing history entry...'));
|
setLoaderText(gettext('Removing history entry...'));
|
||||||
|
|
|
@ -128,9 +128,6 @@ PSYCOPG_SUPPORTED_MULTIRANGE_ARRAY_TYPES = (6155, 6150, 6157, 6151, 6152, 6153)
|
||||||
|
|
||||||
|
|
||||||
def register_global_typecasters():
|
def register_global_typecasters():
|
||||||
# This registers a unicode type caster for datatype 'RECORD'.
|
|
||||||
psycopg.adapters.register_loader(
|
|
||||||
2249, TextLoaderpgAdmin)
|
|
||||||
# This registers a unicode type caster for datatype 'RECORD_ARRAY'.
|
# This registers a unicode type caster for datatype 'RECORD_ARRAY'.
|
||||||
psycopg.adapters.register_loader(
|
psycopg.adapters.register_loader(
|
||||||
2287, TextLoaderpgAdmin)
|
2287, TextLoaderpgAdmin)
|
||||||
|
|
|
@ -282,6 +282,7 @@ class PgadminPage:
|
||||||
option_set_as_required = True
|
option_set_as_required = True
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
self.driver.implicitly_wait(2)
|
||||||
menu_option.click()
|
menu_option.click()
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
if menu_option.get_attribute('data-checked') == is_selected:
|
if menu_option.get_attribute('data-checked') == is_selected:
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
import React, { act } from 'react';
|
||||||
|
|
||||||
import { act, render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import {TestSchema} from './TestSchema.ui';
|
import {TestSchema} from './TestSchema.ui';
|
||||||
|
|
||||||
import SchemaView from '../../../pgadmin/static/js/SchemaView';
|
import SchemaView from '../../../pgadmin/static/js/SchemaView';
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
//////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
import React, { act} from 'react';
|
||||||
|
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
import Theme from '../../../pgadmin/static/js/Theme';
|
import Theme from '../../../pgadmin/static/js/Theme';
|
||||||
|
@ -18,7 +18,6 @@ import axios from 'axios';
|
||||||
import getApiInstance from '../../../pgadmin/static/js/api_instance';
|
import getApiInstance from '../../../pgadmin/static/js/api_instance';
|
||||||
import * as pgUtils from '../../../pgadmin/static/js/utils';
|
import * as pgUtils from '../../../pgadmin/static/js/utils';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
|
|
||||||
const files = [
|
const files = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { render } from '@testing-library/react';
|
import { render } from '@testing-library/react';
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
|
||||||
import Theme from 'sources/Theme';
|
import Theme from 'sources/Theme';
|
||||||
import Wizard from '../../../pgadmin/static/js/helpers/wizard/Wizard';
|
import Wizard from '../../../pgadmin/static/js/helpers/wizard/Wizard';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
|
const { TextEncoder, TextDecoder } = require('util');
|
||||||
|
|
||||||
class BroadcastChannelMock {
|
class BroadcastChannelMock {
|
||||||
onmessage() {/* mock */}
|
onmessage() {/* mock */}
|
||||||
|
@ -93,5 +94,7 @@ Element.prototype.getBoundingClientRect = jest.fn(function () {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
global.TextEncoder = TextEncoder;
|
||||||
|
global.TextDecoder = TextDecoder;
|
||||||
|
|
||||||
jest.setTimeout(18000); // 1 second
|
jest.setTimeout(18000); // 1 second
|
||||||
|
|
600
web/yarn.lock
600
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue