From d784123c04768e96bf156b2f3872335e7caf85c9 Mon Sep 17 00:00:00 2001 From: jiangweidong Date: Wed, 15 Feb 2023 12:12:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=9C=E7=A8=8B=E5=BA=94=E7=94=A8App?= =?UTF-8?q?lets=E6=94=AF=E6=8C=81DBeaver=20(#9537)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: 远程应用Applets支持DBeaver * feat: 更改下载路径 * perf: navicat修改不在这个pr中修改 * perf: add patch.yml --------- Co-authored-by: Eric --- apps/terminal/applets/dbeaver/README.md | 4 + apps/terminal/applets/dbeaver/app.py | 64 +++++++ apps/terminal/applets/dbeaver/common.py | 209 +++++++++++++++++++++ apps/terminal/applets/dbeaver/i18n.yml | 3 + apps/terminal/applets/dbeaver/icon.png | Bin 0 -> 3031 bytes apps/terminal/applets/dbeaver/main.py | 22 +++ apps/terminal/applets/dbeaver/manifest.yml | 17 ++ apps/terminal/applets/dbeaver/patch.yml | 5 + apps/terminal/applets/dbeaver/setup.yml | 5 + 9 files changed, 329 insertions(+) create mode 100644 apps/terminal/applets/dbeaver/README.md create mode 100644 apps/terminal/applets/dbeaver/app.py create mode 100644 apps/terminal/applets/dbeaver/common.py create mode 100644 apps/terminal/applets/dbeaver/i18n.yml create mode 100644 apps/terminal/applets/dbeaver/icon.png create mode 100644 apps/terminal/applets/dbeaver/main.py create mode 100644 apps/terminal/applets/dbeaver/manifest.yml create mode 100644 apps/terminal/applets/dbeaver/patch.yml create mode 100644 apps/terminal/applets/dbeaver/setup.yml diff --git a/apps/terminal/applets/dbeaver/README.md b/apps/terminal/applets/dbeaver/README.md new file mode 100644 index 000000000..f18d62381 --- /dev/null +++ b/apps/terminal/applets/dbeaver/README.md @@ -0,0 +1,4 @@ +## DBeaver + +- 连接数据库应用时,需要下载驱动,可提前离线安装或者连接时按提示安装相应驱动 + diff --git a/apps/terminal/applets/dbeaver/app.py b/apps/terminal/applets/dbeaver/app.py new file mode 100644 index 000000000..710794a98 --- /dev/null +++ b/apps/terminal/applets/dbeaver/app.py @@ -0,0 +1,64 @@ +import time + +from pywinauto import Application + +from common import wait_pid, BaseApplication + + +_default_path = r'C:\Program Files\DBeaver\dbeaver-cli.exe' + + +class AppletApplication(BaseApplication): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.path = _default_path + self.username = self.account.username + self.password = self.account.secret + self.privileged = self.account.privileged + self.host = self.asset.address + self.port = self.asset.get_protocol_port(self.protocol) + self.db = self.asset.spec_info.db_name + self.name = '%s-%s-%s' % (self.host, self.db, int(time.time())) + self.pid = None + self.app = None + + def _get_exec_params(self): + driver = getattr(self, 'driver', self.protocol) + params_string = f'name={self.name}|' \ + f'driver={driver}|' \ + f'host={self.host}|' \ + f'port={self.port}|' \ + f'database={self.db}|' \ + f'"user={self.username}"|' \ + f'password={self.password}|' \ + f'save=false|' \ + f'connect=true' + return params_string + + def _get_mysql_exec_params(self): + params_string = self._get_exec_params() + params_string += '|prop.allowPublicKeyRetrieval=true' + return params_string + + def _get_oracle_exec_params(self): + if self.privileged: + self.username = '%s as sysdba' % self.username + return self._get_exec_params() + + def _get_sqlserver_exec_params(self): + setattr(self, 'driver', 'mssql_jdbc_ms_new') + return self._get_exec_params() + + def run(self): + self.app = Application(backend='uia') + + function = getattr(self, '_get_%s_exec_params' % self.protocol, None) + if function is None: + params = self._get_exec_params() + else: + params = function() + self.app.start('%s -con %s' % (self.path, params), wait_for_idle=False) + self.pid = self.app.process + + def wait(self): + wait_pid(self.pid) diff --git a/apps/terminal/applets/dbeaver/common.py b/apps/terminal/applets/dbeaver/common.py new file mode 100644 index 000000000..010347fe0 --- /dev/null +++ b/apps/terminal/applets/dbeaver/common.py @@ -0,0 +1,209 @@ +import abc +import base64 +import json +import locale +import os +import subprocess +import sys +import time +from subprocess import CREATE_NO_WINDOW + +_blockInput = None +_messageBox = None +if sys.platform == 'win32': + import ctypes + from ctypes import wintypes + import win32ui + + # import win32con + + _messageBox = win32ui.MessageBox + + _blockInput = ctypes.windll.user32.BlockInput + _blockInput.argtypes = [wintypes.BOOL] + _blockInput.restype = wintypes.BOOL + + +def block_input(): + if _blockInput: + _blockInput(True) + + +def unblock_input(): + if _blockInput: + _blockInput(False) + + +def decode_content(content: bytes) -> str: + for encoding_name in ['utf-8', 'gbk', 'gb2312']: + try: + return content.decode(encoding_name) + except Exception as e: + print(e) + encoding_name = locale.getpreferredencoding() + return content.decode(encoding_name) + + +def notify_err_message(msg): + if _messageBox: + _messageBox(msg, 'Error') + + +def check_pid_alive(pid) -> bool: + # tasklist /fi "PID eq 508" /fo csv + # '"映像名称","PID","会话名 ","会话# ","内存使用 "\r\n"wininit.exe","508","Services","0","6,920 K"\r\n' + try: + + csv_ret = subprocess.check_output(["tasklist", "/fi", f'PID eq {pid}', "/fo", "csv"], + creationflags=CREATE_NO_WINDOW) + content = decode_content(csv_ret) + content_list = content.strip().split("\r\n") + if len(content_list) != 2: + print("check pid {} ret invalid: {}".format(pid, content)) + return False + ret_pid = content_list[1].split(",")[1].strip('"') + return str(pid) == ret_pid + except Exception as e: + print("check pid {} err: {}".format(pid, e)) + return False + + +def wait_pid(pid): + while 1: + time.sleep(5) + ok = check_pid_alive(pid) + if not ok: + print("pid {} is not alive".format(pid)) + break + + +class DictObj: + def __init__(self, in_dict: dict): + assert isinstance(in_dict, dict) + for key, val in in_dict.items(): + if isinstance(val, (list, tuple)): + setattr(self, key, [DictObj(x) if isinstance(x, dict) else x for x in val]) + else: + setattr(self, key, DictObj(val) if isinstance(val, dict) else val) + + +class User(DictObj): + id: str + name: str + username: str + + +class Specific(DictObj): + # web + autofill: str + username_selector: str + password_selector: str + submit_selector: str + script: list + + # database + db_name: str + + +class Category(DictObj): + value: str + label: str + + +class Protocol(DictObj): + id: str + name: str + port: int + + +class Asset(DictObj): + id: str + name: str + address: str + protocols: list[Protocol] + category: Category + spec_info: Specific + + def get_protocol_port(self, protocol): + for item in self.protocols: + if item.name == protocol: + return item.port + return None + + +class LabelValue(DictObj): + label: str + value: str + + +class Account(DictObj): + id: str + name: str + username: str + secret: str + privileged: bool + secret_type: LabelValue + + +class Platform(DictObj): + charset: str + name: str + charset: LabelValue + type: LabelValue + + +class Manifest(DictObj): + name: str + version: str + path: str + exec_type: str + connect_type: str + protocols: list[str] + + +def get_manifest_data() -> dict: + current_dir = os.path.dirname(__file__) + manifest_file = os.path.join(current_dir, 'manifest.json') + try: + with open(manifest_file, "r", encoding='utf8') as f: + return json.load(f) + except Exception as e: + print(e) + return {} + + +def read_app_manifest(app_dir) -> dict: + main_json_file = os.path.join(app_dir, "manifest.json") + if not os.path.exists(main_json_file): + return {} + with open(main_json_file, 'r', encoding='utf8') as f: + return json.load(f) + + +def convert_base64_to_dict(base64_str: str) -> dict: + try: + data_json = base64.decodebytes(base64_str.encode('utf-8')).decode('utf-8') + return json.loads(data_json) + except Exception as e: + print(e) + return {} + + +class BaseApplication(abc.ABC): + + def __init__(self, *args, **kwargs): + self.app_name = kwargs.get('app_name', '') + self.protocol = kwargs.get('protocol', '') + self.manifest = Manifest(kwargs.get('manifest', {})) + self.user = User(kwargs.get('user', {})) + self.asset = Asset(kwargs.get('asset', {})) + self.account = Account(kwargs.get('account', {})) + self.platform = Platform(kwargs.get('platform', {})) + + @abc.abstractmethod + def run(self): + raise NotImplementedError('run') + + @abc.abstractmethod + def wait(self): + raise NotImplementedError('wait') diff --git a/apps/terminal/applets/dbeaver/i18n.yml b/apps/terminal/applets/dbeaver/i18n.yml new file mode 100644 index 000000000..fec926496 --- /dev/null +++ b/apps/terminal/applets/dbeaver/i18n.yml @@ -0,0 +1,3 @@ +- zh: + display_name: DBeaver Community + comment: 免费的多平台数据库工具,供开发人员、数据库管理员、分析师和所有需要使用数据库的人使用。 diff --git a/apps/terminal/applets/dbeaver/icon.png b/apps/terminal/applets/dbeaver/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1fd995f21a5af3d6cffa7a8ea8b65d02eb1e831d GIT binary patch literal 3031 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 literal 0 HcmV?d00001 diff --git a/apps/terminal/applets/dbeaver/main.py b/apps/terminal/applets/dbeaver/main.py new file mode 100644 index 000000000..be0ff3585 --- /dev/null +++ b/apps/terminal/applets/dbeaver/main.py @@ -0,0 +1,22 @@ +import sys + +from common import (block_input, unblock_input) +from common import convert_base64_to_dict +from app import AppletApplication + + +def main(): + base64_str = sys.argv[1] + data = convert_base64_to_dict(base64_str) + applet_app = AppletApplication(**data) + block_input() + applet_app.run() + unblock_input() + applet_app.wait() + + +if __name__ == '__main__': + try: + main() + except Exception as e: + print(e) diff --git a/apps/terminal/applets/dbeaver/manifest.yml b/apps/terminal/applets/dbeaver/manifest.yml new file mode 100644 index 000000000..a9b404f60 --- /dev/null +++ b/apps/terminal/applets/dbeaver/manifest.yml @@ -0,0 +1,17 @@ +name: dbeaver +display_name: DBeaver Community +comment: Free multi-platform database tool for developers, database administrators, analysts and all people who need to work with databases. +version: 0.1 +exec_type: python +author: JumpServer Team +type: general +update_policy: always +tags: + - database +protocols: + - mysql + - mariadb + - postgresql + - sqlserver + - oracle + - clickhouse diff --git a/apps/terminal/applets/dbeaver/patch.yml b/apps/terminal/applets/dbeaver/patch.yml new file mode 100644 index 000000000..a5889916f --- /dev/null +++ b/apps/terminal/applets/dbeaver/patch.yml @@ -0,0 +1,5 @@ +type: msi # exe, zip, manual, msi +source: jms:///download/applets/dbeaver-patch.msi +arguments: + - /quiet +destination: diff --git a/apps/terminal/applets/dbeaver/setup.yml b/apps/terminal/applets/dbeaver/setup.yml new file mode 100644 index 000000000..4b31c8e5f --- /dev/null +++ b/apps/terminal/applets/dbeaver/setup.yml @@ -0,0 +1,5 @@ +type: exe # exe, zip, manual +source: jms:///download/applets/dbeaver-ce-22.3.4-x86_64-setup.exe +destination: C:\Program Files\DBeaver +program: C:\Program Files\DBeaver\dbeaver-cli.exe +md5: EDA4440D4E32312DD25C9CE5289A228E