codekingpro/portable-devtools
114k
1##########################################################################2#3# pgAdmin 4 - PostgreSQL Tools4#5# Copyright (C) 2013 - 2024, The pgAdmin Development Team6# This software is released under the PostgreSQL Licence7#8##########################################################################9 10"""A blueprint module providing utility functions for the application."""11 12from pgadmin.utils import driver13from flask import render_template, Response, request, current_app14from flask.helpers import url_for15from flask_babel import gettext16from pgadmin.user_login_check import pga_login_required17from pathlib import Path18from pgadmin.utils import PgAdminModule, replace_binary_path, \19 get_binary_path_versions20from pgadmin.utils.csrf import pgCSRFProtect21from pgadmin.utils.session import cleanup_session_files22from pgadmin.misc.themes import get_all_themes23from pgadmin.utils.constants import MIMETYPE_APP_JS, UTILITIES_ARRAY24from pgadmin.utils.ajax import precondition_required, make_json_response, \25 internal_server_error26from pgadmin.utils.heartbeat import log_server_heartbeat, \27 get_server_heartbeat, stop_server_heartbeat28import config29import time30import json31import os32from urllib.request import urlopen33from pgadmin.settings import get_setting, store_setting34 35MODULE_NAME = 'misc'36 37 38class MiscModule(PgAdminModule):39 LABEL = gettext('Miscellaneous')40 41 def register_preferences(self):42 """43 Register preferences for this module.44 """45 lang_options = []46 for lang in config.LANGUAGES:47 lang_options.append(48 {49 'label': config.LANGUAGES[lang],50 'value': lang51 }52 )53 54 # Register options for the User language settings55 self.preference.register(56 'user_language', 'user_language',57 gettext("User language"), 'options', 'en',58 category_label=gettext('User language'),59 options=lang_options,60 control_props={61 'allowClear': False,62 }63 )64 65 theme_options = []66 67 for theme, theme_data in (get_all_themes()).items():68 theme_options.append({69 'label': theme_data['disp_name']70 .replace('_', ' ')71 .replace('-', ' ')72 .title(),73 'value': theme,74 'preview_src': url_for(75 'static', filename='js/generated/img/' +76 theme_data['preview_img']77 )78 })79 80 self.preference.register(81 'themes', 'theme',82 gettext("Theme"), 'options', 'standard',83 category_label=gettext('Themes'),84 options=theme_options,85 control_props={86 'allowClear': False,87 },88 help_str=gettext(89 'A refresh is required to apply the theme. Above is the '90 'preview of the theme'91 )92 )93 94 def get_exposed_url_endpoints(self):95 """96 Returns:97 list: a list of url endpoints exposed to the client.98 """99 return ['misc.ping', 'misc.index', 'misc.cleanup',100 'misc.validate_binary_path', 'misc.log_heartbeat',101 'misc.stop_heartbeat', 'misc.get_heartbeat',102 'misc.upgrade_check']103 104 def register(self, app, options):105 """106 Override the default register function to automagically register107 sub-modules at once.108 """109 from .bgprocess import blueprint as module110 self.submodules.append(module)111 112 from .cloud import blueprint as module113 self.submodules.append(module)114 115 from .dependencies import blueprint as module116 self.submodules.append(module)117 118 from .dependents import blueprint as module119 self.submodules.append(module)120 121 from .file_manager import blueprint as module122 self.submodules.append(module)123 124 from .statistics import blueprint as module125 self.submodules.append(module)126 127 super().register(app, options)128 129 130# Initialise the module131blueprint = MiscModule(MODULE_NAME, __name__)132 133 134##########################################################################135# A special URL used to "ping" the server136##########################################################################137@blueprint.route("/", endpoint='index')138def index():139 return ''140 141 142##########################################################################143# A special URL used to "ping" the server144##########################################################################145@blueprint.route("/ping")146@pgCSRFProtect.exempt147def ping():148 """Generate a "PING" response to indicate that the server is alive."""149 return "PING"150 151 152# For Garbage Collecting closed connections153@blueprint.route("/cleanup", methods=['POST'])154@pgCSRFProtect.exempt155def cleanup():156 driver.ping()157 # Cleanup session files.158 cleanup_session_files()159 return ""160 161 162@blueprint.route("/heartbeat/log", methods=['POST'])163@pgCSRFProtect.exempt164def log_heartbeat():165 data = None166 if hasattr(request.data, 'decode'):167 data = request.data.decode('utf-8')168 169 if data != '':170 data = json.loads(data)171 172 status, msg = log_server_heartbeat(data)173 if status:174 return make_json_response(data=msg, status=200)175 else:176 return make_json_response(data=msg, status=404)177 178 179@blueprint.route("/heartbeat/stop", methods=['POST'])180@pgCSRFProtect.exempt181def stop_heartbeat():182 data = None183 if hasattr(request.data, 'decode'):184 data = request.data.decode('utf-8')185 186 if data != '':187 data = json.loads(data)188 189 _, msg = stop_server_heartbeat(data)190 return make_json_response(data=msg,191 status=200)192 193 194@blueprint.route("/get_heartbeat/<int:sid>", methods=['GET'])195@pgCSRFProtect.exempt196def get_heartbeat(sid):197 heartbeat_data = get_server_heartbeat(sid)198 return make_json_response(data=heartbeat_data,199 status=200)200 201 202##########################################################################203# A special URL used to shut down the server204##########################################################################205@blueprint.route("/shutdown", methods=('get', 'post'))206@pgCSRFProtect.exempt207def shutdown():208 if config.SERVER_MODE is not True:209 func = request.environ.get('werkzeug.server.shutdown')210 if func is None:211 raise RuntimeError('Not running with the Werkzeug Server')212 func()213 return 'SHUTDOWN'214 else:215 return ''216 217 218##########################################################################219# A special URL used to validate the binary path220##########################################################################221@blueprint.route("/validate_binary_path",222 endpoint="validate_binary_path",223 methods=["POST"])224@pga_login_required225def validate_binary_path():226 """227 This function is used to validate the specified utilities path by228 running the utilities with their versions.229 """230 data = None231 if hasattr(request.data, 'decode'):232 data = request.data.decode('utf-8')233 234 if data != '':235 data = json.loads(data)236 237 version_str = ''238 239 # Do not allow storage dir as utility path240 if 'utility_path' in data and data['utility_path'] is not None and \241 Path(config.STORAGE_DIR) != Path(data['utility_path']) and \242 Path(config.STORAGE_DIR) not in Path(data['utility_path']).parents:243 binary_versions = get_binary_path_versions(data['utility_path'])244 for utility, version in binary_versions.items():245 if version is None:246 version_str += "<b>" + utility + ":</b> " + \247 "not found on the specified binary path.<br/>"248 else:249 version_str += "<b>" + utility + ":</b> " + version + "<br/>"250 else:251 return precondition_required(gettext('Invalid binary path.'))252 253 return make_json_response(data=gettext(version_str), status=200)254 255 256@blueprint.route("/upgrade_check", endpoint="upgrade_check",257 methods=['GET'])258@pga_login_required259def upgrade_check():260 # Get the current version info from the website, and flash a message if261 # the user is out of date, and the check is enabled.262 ret = {263 "outdated": False,264 }265 if config.UPGRADE_CHECK_ENABLED:266 last_check = get_setting('LastUpdateCheck', default='0')267 today = time.strftime('%Y%m%d')268 if int(last_check) < int(today):269 data = None270 url = '%s?version=%s' % (271 config.UPGRADE_CHECK_URL, config.APP_VERSION)272 current_app.logger.debug('Checking version data at: %s' % url)273 try:274 # Do not wait for more than 5 seconds.275 # It stuck on rendering the browser.html, while working in the276 # broken network.277 if os.path.exists(config.CA_FILE):278 response = urlopen(url, data, 5, cafile=config.CA_FILE)279 else:280 response = urlopen(url, data, 5)281 current_app.logger.debug(282 'Version check HTTP response code: %d' % response.getcode()283 )284 285 if response.getcode() == 200:286 data = json.loads(response.read().decode('utf-8'))287 current_app.logger.debug('Response data: %s' % data)288 except Exception:289 current_app.logger.exception(290 'Exception when checking for update')291 return internal_server_error('Failed to check for update')292 293 if data is not None and \294 data[config.UPGRADE_CHECK_KEY]['version_int'] > \295 config.APP_VERSION_INT:296 ret = {297 "outdated": True,298 "current_version": config.APP_VERSION,299 "upgrade_version": data[config.UPGRADE_CHECK_KEY][300 'version'],301 "product_name": config.APP_NAME,302 "download_url": data[config.UPGRADE_CHECK_KEY][303 'download_url']304 }305 306 store_setting('LastUpdateCheck', today)307 return make_json_response(data=ret)308 