mirror of https://github.com/jumpserver/jumpserver
feat: 远程应用Applets支持DBeaver (#9537)
* perf: 远程应用Applets支持DBeaver * feat: 更改下载路径 * perf: navicat修改不在这个pr中修改 * perf: add patch.yml --------- Co-authored-by: Eric <xplzv@126.com>pull/9562/head
parent
e25306608b
commit
d784123c04
@ -0,0 +1,4 @@
|
||||
## DBeaver
|
||||
|
||||
- 连接数据库应用时,需要下载驱动,可提前离线安装或者连接时按提示安装相应驱动
|
||||
|
@ -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)
|
@ -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')
|
@ -0,0 +1,3 @@
|
||||
- zh:
|
||||
display_name: DBeaver Community
|
||||
comment: 免费的多平台数据库工具,供开发人员、数据库管理员、分析师和所有需要使用数据库的人使用。
|
After Width: | Height: | Size: 3.0 KiB |
@ -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)
|
@ -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
|
@ -0,0 +1,5 @@
|
||||
type: msi # exe, zip, manual, msi
|
||||
source: jms:///download/applets/dbeaver-patch.msi
|
||||
arguments:
|
||||
- /quiet
|
||||
destination:
|
@ -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
|
Loading…
Reference in new issue