From 2196d127b733603051bb3c7d685d1ea8558fe038 Mon Sep 17 00:00:00 2001 From: orohi Date: Sun, 26 Jul 2026 00:39:33 +0300 Subject: [PATCH] Fix Internal Server Error after SSH server add (server_info must be a dict). Co-authored-by: Cursor --- app.py | 15 +++++++++++++-- db/store.py | 35 +++++++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/app.py b/app.py index 51e1de4..7e9bdfa 100644 --- a/app.py +++ b/app.py @@ -2273,11 +2273,17 @@ async def api_add_server(request: Request, req: AddServerRequest): ssh = SSHManager(host, req.ssh_port, username, req.password, req.private_key) try: ssh.connect() - server_info = ssh.test_connection() + raw_info = ssh.test_connection() ssh.disconnect() except Exception as e: return JSONResponse({'error': f'Connection failed: {str(e)}'}, status_code=400) + # Always store as an object — legacy test_connection() returns a plain string + if isinstance(raw_info, dict): + server_info = raw_info + else: + server_info = {'uname': str(raw_info or '').strip()} + server = { 'name': name, 'host': host, 'ssh_port': req.ssh_port, 'username': username, 'password': req.password, @@ -2328,11 +2334,16 @@ async def api_edit_server(request: Request, server_id: int, req: EditServerReque ssh = SSHManager(new_host, new_port, new_user, new_pass, new_key) try: ssh.connect() - server_info = ssh.test_connection() + raw_info = ssh.test_connection() ssh.disconnect() except Exception as e: return JSONResponse({'error': f'Connection failed: {e}'}, status_code=400) + if isinstance(raw_info, dict): + server_info = raw_info + else: + server_info = {'uname': str(raw_info or '').strip()} + server['name'] = new_name server['host'] = new_host server['ssh_port'] = new_port diff --git a/db/store.py b/db/store.py index 666c1c3..641d3b2 100644 --- a/db/store.py +++ b/db/store.py @@ -94,6 +94,33 @@ def _merge_settings(raw: Optional[dict]) -> dict: return settings +def _as_dict(value, default=None): + """Coerce JSONB / legacy values to a plain dict.""" + if default is None: + default = {} + if value is None: + return dict(default) + if isinstance(value, dict): + return dict(value) + # Legacy: ssh.test_connection() used to return a plain string + if isinstance(value, str): + text = value.strip() + if not text: + return dict(default) + if text.startswith('{') or text.startswith('['): + try: + parsed = json.loads(text) + if isinstance(parsed, dict): + return parsed + except Exception: + pass + return {'raw': text} + try: + return dict(value) + except Exception: + return {'raw': str(value)} + + def _row_to_server(row) -> dict: return { 'name': row['name'] or '', @@ -102,8 +129,8 @@ def _row_to_server(row) -> dict: 'username': row['username'] or '', 'password': row['password'], 'private_key': row['private_key'], - 'server_info': dict(row['server_info'] or {}), - 'protocols': dict(row['protocols'] or {}), + 'server_info': _as_dict(row['server_info']), + 'protocols': _as_dict(row['protocols']), } @@ -241,8 +268,8 @@ def save_data(data: dict) -> None: server.get('username') or '', server.get('password'), server.get('private_key'), - Jsonb(server.get('server_info') or {}), - Jsonb(server.get('protocols') or {}), + Jsonb(_as_dict(server.get('server_info'))), + Jsonb(_as_dict(server.get('protocols'))), ), )