2015-06-29 01:58:41 -05:00
|
|
|
##########################################################################
|
|
|
|
#
|
|
|
|
# pgAdmin 4 - PostgreSQL Tools
|
|
|
|
#
|
2023-01-02 00:23:55 -06:00
|
|
|
# Copyright (C) 2013 - 2023, The pgAdmin Development Team
|
2015-06-29 01:58:41 -05:00
|
|
|
# This software is released under the PostgreSQL Licence
|
|
|
|
#
|
|
|
|
##########################################################################
|
|
|
|
|
|
|
|
"""A blueprint module providing utility functions for the application."""
|
|
|
|
|
2022-11-18 22:43:41 -06:00
|
|
|
from pgadmin.utils import driver
|
2022-11-30 00:32:45 -06:00
|
|
|
from flask import url_for, render_template, Response, request, current_app
|
2021-11-24 05:52:57 -06:00
|
|
|
from flask_babel import gettext
|
2022-11-30 00:32:45 -06:00
|
|
|
from flask_security import login_required
|
2023-09-20 00:47:07 -05:00
|
|
|
from pgadmin.utils import PgAdminModule, replace_binary_path, \
|
|
|
|
get_binary_path_versions
|
2019-05-28 00:29:51 -05:00
|
|
|
from pgadmin.utils.csrf import pgCSRFProtect
|
2018-10-09 05:34:13 -05:00
|
|
|
from pgadmin.utils.session import cleanup_session_files
|
2019-11-11 07:17:43 -06:00
|
|
|
from pgadmin.misc.themes import get_all_themes
|
2021-06-07 10:06:34 -05:00
|
|
|
from pgadmin.utils.constants import MIMETYPE_APP_JS, UTILITIES_ARRAY
|
2021-06-04 07:25:35 -05:00
|
|
|
from pgadmin.utils.ajax import precondition_required, make_json_response
|
2023-01-30 04:09:34 -06:00
|
|
|
from pgadmin.utils.heartbeat import log_server_heartbeat,\
|
|
|
|
get_server_heartbeat, stop_server_heartbeat
|
2016-05-15 14:37:52 -05:00
|
|
|
import config
|
2021-06-04 07:25:35 -05:00
|
|
|
import subprocess
|
|
|
|
import os
|
|
|
|
import json
|
2015-10-20 02:03:18 -05:00
|
|
|
|
|
|
|
MODULE_NAME = 'misc'
|
2015-06-29 01:58:41 -05:00
|
|
|
|
2016-05-15 14:37:52 -05:00
|
|
|
|
|
|
|
class MiscModule(PgAdminModule):
|
2019-11-07 07:21:03 -06:00
|
|
|
LABEL = gettext('Miscellaneous')
|
|
|
|
|
2016-05-15 14:37:52 -05:00
|
|
|
def get_own_stylesheets(self):
|
|
|
|
stylesheets = []
|
|
|
|
return stylesheets
|
|
|
|
|
2017-03-24 09:20:10 -05:00
|
|
|
def register_preferences(self):
|
|
|
|
"""
|
|
|
|
Register preferences for this module.
|
|
|
|
"""
|
|
|
|
lang_options = []
|
|
|
|
for lang in config.LANGUAGES:
|
2018-02-09 06:57:37 -06:00
|
|
|
lang_options.append(
|
|
|
|
{
|
|
|
|
'label': config.LANGUAGES[lang],
|
|
|
|
'value': lang
|
|
|
|
}
|
|
|
|
)
|
2017-03-24 09:20:10 -05:00
|
|
|
|
|
|
|
# Register options for the User language settings
|
2019-11-07 07:21:03 -06:00
|
|
|
self.preference.register(
|
|
|
|
'user_language', 'user_language',
|
2018-02-09 06:57:37 -06:00
|
|
|
gettext("User language"), 'options', 'en',
|
|
|
|
category_label=gettext('User language'),
|
2022-03-21 02:59:26 -05:00
|
|
|
options=lang_options,
|
|
|
|
control_props={
|
|
|
|
'allowClear': False,
|
|
|
|
}
|
2017-03-24 09:20:10 -05:00
|
|
|
)
|
|
|
|
|
2019-11-07 07:21:03 -06:00
|
|
|
theme_options = []
|
|
|
|
|
2019-11-11 07:17:43 -06:00
|
|
|
for theme, theme_data in (get_all_themes()).items():
|
2019-11-07 07:21:03 -06:00
|
|
|
theme_options.append({
|
2019-11-11 07:17:43 -06:00
|
|
|
'label': theme_data['disp_name']
|
2019-11-07 07:21:03 -06:00
|
|
|
.replace('_', ' ')
|
|
|
|
.replace('-', ' ')
|
|
|
|
.title(),
|
|
|
|
'value': theme,
|
|
|
|
'preview_src': url_for(
|
|
|
|
'static', filename='js/generated/img/' +
|
2019-11-11 07:17:43 -06:00
|
|
|
theme_data['preview_img']
|
2019-11-07 07:21:03 -06:00
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
self.preference.register(
|
|
|
|
'themes', 'theme',
|
|
|
|
gettext("Theme"), 'options', 'standard',
|
|
|
|
category_label=gettext('Themes'),
|
|
|
|
options=theme_options,
|
2022-03-21 02:59:26 -05:00
|
|
|
control_props={
|
|
|
|
'allowClear': False,
|
|
|
|
},
|
2019-11-07 07:21:03 -06:00
|
|
|
help_str=gettext(
|
2022-03-21 02:59:26 -05:00
|
|
|
'A refresh is required to apply the theme. Above is the '
|
2019-11-07 07:21:03 -06:00
|
|
|
'preview of the theme'
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2017-06-12 01:31:22 -05:00
|
|
|
def get_exposed_url_endpoints(self):
|
|
|
|
"""
|
|
|
|
Returns:
|
|
|
|
list: a list of url endpoints exposed to the client.
|
|
|
|
"""
|
2021-06-04 07:25:35 -05:00
|
|
|
return ['misc.ping', 'misc.index', 'misc.cleanup',
|
2023-01-30 04:09:34 -06:00
|
|
|
'misc.validate_binary_path', 'misc.log_heartbeat',
|
|
|
|
'misc.stop_heartbeat', 'misc.get_heartbeat']
|
2017-06-12 01:31:22 -05:00
|
|
|
|
2022-06-30 00:36:50 -05:00
|
|
|
def register(self, app, options):
|
|
|
|
"""
|
|
|
|
Override the default register function to automagically register
|
|
|
|
sub-modules at once.
|
|
|
|
"""
|
|
|
|
from .bgprocess import blueprint as module
|
|
|
|
self.submodules.append(module)
|
|
|
|
|
|
|
|
from .cloud import blueprint as module
|
|
|
|
self.submodules.append(module)
|
|
|
|
|
|
|
|
from .dependencies import blueprint as module
|
|
|
|
self.submodules.append(module)
|
|
|
|
|
|
|
|
from .dependents import blueprint as module
|
|
|
|
self.submodules.append(module)
|
|
|
|
|
|
|
|
from .file_manager import blueprint as module
|
|
|
|
self.submodules.append(module)
|
|
|
|
|
|
|
|
from .sql import blueprint as module
|
|
|
|
self.submodules.append(module)
|
|
|
|
|
|
|
|
from .statistics import blueprint as module
|
|
|
|
self.submodules.append(module)
|
|
|
|
|
2022-11-18 22:43:41 -06:00
|
|
|
super().register(app, options)
|
2022-06-30 00:36:50 -05:00
|
|
|
|
2016-05-15 14:37:52 -05:00
|
|
|
|
2015-06-29 01:58:41 -05:00
|
|
|
# Initialise the module
|
2016-05-15 14:37:52 -05:00
|
|
|
blueprint = MiscModule(MODULE_NAME, __name__)
|
|
|
|
|
2015-06-29 01:58:41 -05:00
|
|
|
|
|
|
|
##########################################################################
|
|
|
|
# A special URL used to "ping" the server
|
|
|
|
##########################################################################
|
2017-07-18 09:13:16 -05:00
|
|
|
@blueprint.route("/", endpoint='index')
|
2016-05-15 14:37:52 -05:00
|
|
|
def index():
|
|
|
|
return ''
|
2015-10-20 02:03:18 -05:00
|
|
|
|
|
|
|
|
2016-05-15 14:37:52 -05:00
|
|
|
##########################################################################
|
|
|
|
# A special URL used to "ping" the server
|
|
|
|
##########################################################################
|
2018-07-05 05:12:03 -05:00
|
|
|
@blueprint.route("/ping")
|
2019-05-28 06:35:54 -05:00
|
|
|
@pgCSRFProtect.exempt
|
2015-06-29 01:58:41 -05:00
|
|
|
def ping():
|
|
|
|
"""Generate a "PING" response to indicate that the server is alive."""
|
|
|
|
return "PING"
|
2016-05-15 14:37:52 -05:00
|
|
|
|
|
|
|
|
2018-07-05 05:12:03 -05:00
|
|
|
# For Garbage Collecting closed connections
|
|
|
|
@blueprint.route("/cleanup", methods=['POST'])
|
2019-05-28 00:29:51 -05:00
|
|
|
@pgCSRFProtect.exempt
|
2018-07-05 05:12:03 -05:00
|
|
|
def cleanup():
|
|
|
|
driver.ping()
|
2018-10-09 05:34:13 -05:00
|
|
|
# Cleanup session files.
|
|
|
|
cleanup_session_files()
|
2018-07-05 05:12:03 -05:00
|
|
|
return ""
|
|
|
|
|
|
|
|
|
2023-01-30 04:09:34 -06:00
|
|
|
@blueprint.route("/heartbeat/log", methods=['POST'])
|
2023-01-19 04:27:02 -06:00
|
|
|
@pgCSRFProtect.exempt
|
2023-01-30 04:09:34 -06:00
|
|
|
def log_heartbeat():
|
2023-01-19 04:27:02 -06:00
|
|
|
data = None
|
|
|
|
if hasattr(request.data, 'decode'):
|
|
|
|
data = request.data.decode('utf-8')
|
|
|
|
|
|
|
|
if data != '':
|
|
|
|
data = json.loads(data)
|
|
|
|
|
2023-01-30 04:09:34 -06:00
|
|
|
status, msg = log_server_heartbeat(data)
|
|
|
|
if status:
|
|
|
|
return make_json_response(data=msg, status=200)
|
|
|
|
else:
|
|
|
|
return make_json_response(data=msg, status=404)
|
|
|
|
|
|
|
|
|
|
|
|
@blueprint.route("/heartbeat/stop", methods=['POST'])
|
|
|
|
@pgCSRFProtect.exempt
|
|
|
|
def stop_heartbeat():
|
|
|
|
data = None
|
|
|
|
if hasattr(request.data, 'decode'):
|
|
|
|
data = request.data.decode('utf-8')
|
|
|
|
|
|
|
|
if data != '':
|
|
|
|
data = json.loads(data)
|
|
|
|
|
|
|
|
status, msg = stop_server_heartbeat(data)
|
|
|
|
return make_json_response(data=msg,
|
2023-01-19 04:27:02 -06:00
|
|
|
status=200)
|
|
|
|
|
|
|
|
|
|
|
|
@blueprint.route("/get_heartbeat/<int:sid>", methods=['GET'])
|
|
|
|
@pgCSRFProtect.exempt
|
|
|
|
def get_heartbeat(sid):
|
|
|
|
heartbeat_data = get_server_heartbeat(sid)
|
|
|
|
return make_json_response(data=heartbeat_data,
|
|
|
|
status=200)
|
|
|
|
|
|
|
|
|
2016-05-15 14:37:52 -05:00
|
|
|
@blueprint.route("/explain/explain.js")
|
|
|
|
def explain_js():
|
|
|
|
"""
|
|
|
|
explain_js()
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
javascript for the explain module
|
|
|
|
"""
|
2016-05-16 00:58:23 -05:00
|
|
|
return Response(
|
|
|
|
response=render_template(
|
2018-02-09 06:57:37 -06:00
|
|
|
"explain/js/explain.js",
|
|
|
|
_=gettext
|
2016-05-16 00:58:23 -05:00
|
|
|
),
|
|
|
|
status=200,
|
2020-08-20 09:56:51 -05:00
|
|
|
mimetype=MIMETYPE_APP_JS
|
2016-05-16 00:58:23 -05:00
|
|
|
)
|
2018-03-15 02:56:24 -05:00
|
|
|
|
2018-03-15 05:43:11 -05:00
|
|
|
|
2018-03-15 02:56:24 -05:00
|
|
|
##########################################################################
|
2018-06-08 02:45:02 -05:00
|
|
|
# A special URL used to shut down the server
|
2018-03-15 02:56:24 -05:00
|
|
|
##########################################################################
|
|
|
|
@blueprint.route("/shutdown", methods=('get', 'post'))
|
2019-05-28 06:35:54 -05:00
|
|
|
@pgCSRFProtect.exempt
|
2018-03-15 02:56:24 -05:00
|
|
|
def shutdown():
|
|
|
|
if config.SERVER_MODE is not True:
|
|
|
|
func = request.environ.get('werkzeug.server.shutdown')
|
|
|
|
if func is None:
|
|
|
|
raise RuntimeError('Not running with the Werkzeug Server')
|
|
|
|
func()
|
|
|
|
return 'SHUTDOWN'
|
|
|
|
else:
|
|
|
|
return ''
|
2021-06-04 07:25:35 -05:00
|
|
|
|
|
|
|
|
|
|
|
##########################################################################
|
|
|
|
# A special URL used to validate the binary path
|
|
|
|
##########################################################################
|
|
|
|
@blueprint.route("/validate_binary_path",
|
|
|
|
endpoint="validate_binary_path",
|
|
|
|
methods=["POST"])
|
2022-12-13 04:56:35 -06:00
|
|
|
@login_required
|
2021-06-04 07:25:35 -05:00
|
|
|
def validate_binary_path():
|
|
|
|
"""
|
|
|
|
This function is used to validate the specified utilities path by
|
2022-11-30 00:32:45 -06:00
|
|
|
running the utilities with their versions.
|
2021-06-04 07:25:35 -05:00
|
|
|
"""
|
|
|
|
data = None
|
|
|
|
if hasattr(request.data, 'decode'):
|
|
|
|
data = request.data.decode('utf-8')
|
|
|
|
|
|
|
|
if data != '':
|
|
|
|
data = json.loads(data)
|
|
|
|
|
|
|
|
version_str = ''
|
|
|
|
if 'utility_path' in data and data['utility_path'] is not None:
|
2023-09-20 00:47:07 -05:00
|
|
|
binary_versions = get_binary_path_versions(data['utility_path'])
|
|
|
|
for utility, version in binary_versions.items():
|
|
|
|
if version is None:
|
2021-06-04 07:25:35 -05:00
|
|
|
version_str += "<b>" + utility + ":</b> " + \
|
|
|
|
"not found on the specified binary path.<br/>"
|
2023-09-20 00:47:07 -05:00
|
|
|
else:
|
|
|
|
version_str += "<b>" + utility + ":</b> " + version + "<br/>"
|
2021-06-04 07:25:35 -05:00
|
|
|
else:
|
|
|
|
return precondition_required(gettext('Invalid binary path.'))
|
|
|
|
|
|
|
|
return make_json_response(data=gettext(version_str), status=200)
|