From 6761df00ca01c569083320c0b97c4b8c7c115602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=91=D0=BE=D0=B1?= =?UTF-8?q?=D1=8B=D1=80=D0=B5=D0=B2?= Date: Wed, 29 Apr 2026 16:23:11 +0300 Subject: [PATCH] routers: add optional SSH port Add an optional SSH port field in the routers UI and use it for push_all SSH connections. Made-with: Cursor --- server/main.py | 15 +++++++++++++++ server/templates/index.html | 18 ++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/server/main.py b/server/main.py index 3834c41..7314752 100644 --- a/server/main.py +++ b/server/main.py @@ -65,6 +65,13 @@ async def _ssh_on_router(rcfg: dict, remote_cmd: str, timeout: int = 45) -> tupl return 1, "", "нет IP и нет тоннеля (добавь IP или настрой тоннель)" ssh_host = ip extra_args = [] + # Optional direct SSH port (default: 22). Tunnel uses its own port args above. + try: + p = int(rcfg.get("ssh_port") or 22) + if p and p != 22: + extra_args = ["-p", str(p)] + except Exception: + pass user = rcfg.get("user") or config.SSH_USER pwd = rcfg.get("password") or config.SSH_PASS try: @@ -103,8 +110,16 @@ async def _push_one_router(server_url: str, router_key: str, rcfg: dict) -> dict "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=10", "-p", str(int(tunnel_port)), f"{user}@127.0.0.1", cmd] else: + port_args: list[str] = [] + try: + p = int(rcfg.get("ssh_port") or 22) + if p and p != 22: + port_args = ["-p", str(p)] + except Exception: + port_args = [] ssh_cmd = ["sshpass", "-p", pwd, "ssh", "-o", "StrictHostKeyChecking=no", "-o", "ConnectTimeout=10", + *port_args, f"{user}@{ip}", cmd] try: r = await asyncio.to_thread(subprocess.run, ssh_cmd, capture_output=True, text=True, timeout=60) diff --git a/server/templates/index.html b/server/templates/index.html index 124a916..d446678 100644 --- a/server/templates/index.html +++ b/server/templates/index.html @@ -207,6 +207,7 @@ input[type=text]:focus,input[type=password]:focus{border-color:var(--accent)} + @@ -430,10 +431,17 @@ async function loadRouters(){ const R=await(await fetch('/api/routers')).json(); const el=document.getElementById('routers-list'); if(!Object.keys(R).length){ el.innerHTML='Роутеры не добавлены'; return; } + const _hostWithPort = (c) => { + const host=(c.ip||'').toString().trim(); + const p=(c.ssh_port!=null && c.ssh_port!=='' && !Number.isNaN(Number(c.ssh_port))) ? Number(c.ssh_port) : null; + if(!host) return ''; + if(!p) return host; + return `${host}:${p}`; + }; el.innerHTML=Object.entries(R).map(([n,c])=>{ const enc=encodeURIComponent(n); return `
-
${_escHtml(n)} ${c.tunnel_port?`⇄ тоннель :${c.tunnel_port}`:`— ${_escHtml(c.ip||'—')}`}
+
${_escHtml(n)} ${c.tunnel_port?`⇄ тоннель :${c.tunnel_port}`:`— ${_escHtml(_hostWithPort(c)||'—')}`}
@@ -443,7 +451,13 @@ async function loadRouters(){
`; }).join(''); } -async function addRouter(){ const n=document.getElementById('r-name').value.trim(); if(!n)return; const b={ip:document.getElementById('r-ip').value.trim(),user:document.getElementById('r-user').value||'root',password:document.getElementById('r-pass').value}; try{ await fetch('/api/routers/'+encodeURIComponent(n),{method:'POST',headers:authHdr(),body:JSON.stringify(b)}); sm('rm','ok','✅ '+n); loadRouters(); }catch(e){sm('rm','err','❌ '+e)} } +async function addRouter(){ + const n=document.getElementById('r-name').value.trim(); if(!n)return; + const portRaw=(document.getElementById('r-port')?.value||'').trim(); + const p = portRaw ? Number(portRaw) : null; + const ssh_port = (p && Number.isFinite(p) && p > 0) ? Math.trunc(p) : null; + const b={ip:document.getElementById('r-ip').value.trim(),user:document.getElementById('r-user').value||'root',ssh_port:ssh_port,password:document.getElementById('r-pass').value}; + try{ await fetch('/api/routers/'+encodeURIComponent(n),{method:'POST',headers:authHdr(),body:JSON.stringify(b)}); sm('rm','ok','✅ '+n); loadRouters(); }catch(e){sm('rm','err','❌ '+e)} } async function addAndTunnel(){ const n=document.getElementById('r-name').value.trim(); if(!n){sm('rm','err','❌ Введи имя роутера');return;}