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 000000000..1fd995f21 Binary files /dev/null and b/apps/terminal/applets/dbeaver/icon.png differ 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