diff --git a/diagnostic/index.html b/diagnostic/index.html index 4e71bda..4ba62ab 100644 --- a/diagnostic/index.html +++ b/diagnostic/index.html @@ -5378,6 +5378,20 @@ renderSatellites(); send({ action: 'sat_discover', satellite: id }); } + + /** Geraetenamen vergeben. Danach kann ARIA das Geraet so ansprechen + * ("mach mir Port 5000 von duffy-desktop auf") statt ueber eine IP. + * Gespeichert wird an der MAC — ein IP-Wechsel per DHCP verliert den + * Namen dann nicht. */ + function renameSatDevice(satId, mac, ip, current) { + const name = prompt( + 'Name für ' + (ip || mac) + '\n\n' + + 'ARIA spricht das Gerät danach so an. Leer lassen = Namen entfernen.', + current || ''); + if (name === null) return; // abgebrochen + send({ action: 'sat_name_set', satellite: satId, mac: mac, ip: ip, name: name.trim() }); + setTimeout(() => scanSatellite(satId), 400); + } function renderSatellites() { const box = document.getElementById('sat-list'); if (!box) return; @@ -5409,6 +5423,12 @@ if (d.adbCapable) tags.push('ADB'); if (d.mac) tags.push(escapeHtml(d.mac)); const tagStr = tags.length ? ' (' + tags.join(' · ') + ')' : ''; + // Namen vergeben — danach ist das Geraet fuer ARIA ansprechbar + // ("duffy-desktop" statt einer IP). + const nameBtn = (d.ip || d.mac) ? '' : ''; const hasCreds = d.ip && (satCreds[s.id] || {})[d.ip] && (satCreds[s.id][d.ip] || []).length; const keyBtn = d.ip ? '' : ''; - return '
' + - '
' + - '' + escapeHtml(d.name || d.id || '?') + ' ' + + // Der Rechner, auf dem der Satellit selbst laeuft. Er ist ein + // Host wie jeder andere (und oft der interessanteste — dort + // laufen meist noch weitere Dienste), nur eben zusaetzlich + // Traeger des Satelliten. Darum normal auflisten + markieren. + const satBadge = d.isSatellite + ? '🛰️' + : ''; + const rowBg = d.isSatellite ? '#111426' : '#0D0D1A'; + const rowBorder = d.isSatellite ? ';border-left:2px solid #0096FF' : ''; + return '
' + + '
' + satBadge + + '' + escapeHtml(d.name || d.hostname || d.id || '?') + ' ' + '[' + escapeHtml(d.type || '?') + '] ' + '' + escapeHtml(d.ip || '') + '' + tagStr + - '
' + keyBtn + apkBtn + + '
' + nameBtn + keyBtn + apkBtn + '
'; }).join('') + '
'; } diff --git a/diagnostic/server.js b/diagnostic/server.js index 1dc00fc..76c5dae 100644 --- a/diagnostic/server.js +++ b/diagnostic/server.js @@ -3005,6 +3005,14 @@ wss.on("connection", (ws) => { sendToRVS_raw({ type: "sat_discover", payload: { satellite: msg.satellite || "", force: true }, timestamp: Date.now() }); + } else if (msg.action === "sat_name_set") { + // Geraetename vergeben/entfernen. Der Satellit haengt ihn an die MAC, + // damit ein IP-Wechsel den Namen nicht verliert. + sendToRVS_raw({ type: "sat_name_set", + payload: { satellite: msg.satellite || "", mac: msg.mac || "", + ip: msg.ip || "", name: msg.name || "", + requestId: "dn_" + Date.now() }, + timestamp: Date.now() }); } else if (msg.action === "sat_creds_list") { sendToRVS_raw({ type: "sat_creds_list", payload: { satellite: msg.satellite || "", requestId: "dc_" + Date.now() }, diff --git a/satellite/.env.example b/satellite/.env.example index 1b1d1e5..958112f 100644 --- a/satellite/.env.example +++ b/satellite/.env.example @@ -96,6 +96,15 @@ DEVICE_CACHE_TTL_SEC=120 # wie lange ein Inventar als "frisch" gilt # Bridge macht daraus einen lokalen Port (aria-bridge:19000...). # # DEFAULT AUS: das ist ein Weg in dein Netz, den schaltet man bewusst ein. +# Soll der Rechner, auf dem der Satellit laeuft, SELBST in der Geraeteliste +# auftauchen? Er wird per mDNS/ARP nicht gefunden (man findet sich nicht +# selbst), ist aber oft der interessanteste Host — dort laufen typischerweise +# weitere Dienste (FreeCAD-MCP, Web-App, Dateiserver). Er erscheint dann als +# normales Geraet, zusaetzlich mit 🛰️ gekennzeichnet, und laesst sich genauso +# ansprechen. Nebeneffekt: er gilt damit als "gefunden" und ist ohne Eintrag in +# TUNNEL_ALLOW tunnelbar. +LIST_SELF=true + TUNNEL_ENABLED=false # Welche Ziele sind erlaubt? LEER = nur Geraete, die der Satellit bei der Suche diff --git a/satellite/satellite.py b/satellite/satellite.py index 0d0fabe..c7e0269 100644 --- a/satellite/satellite.py +++ b/satellite/satellite.py @@ -151,6 +151,12 @@ CONTROL_ALLOWLIST = [ # DEFAULT AUS. Das ist ein Loch in ein fremdes Netz — das schaltet man bewusst # ein, nicht aus Versehen. TUNNEL_ENABLED = _env_bool("TUNNEL_ENABLED", False) + +# Soll der Rechner, auf dem der Satellit laeuft, selbst in der Geraeteliste +# auftauchen? Default JA — meist ist er ein ganz normaler Host mit weiteren +# Diensten. Wer den Satelliten-Rechner bewusst heraushalten will, setzt das +# auf false. +LIST_SELF = _env_bool("LIST_SELF", True) # Welche Ziele? Leer = nur Geraete, die der Satellit bei der Suche auch # wirklich GEFUNDEN hat (beliebiger Port). Damit kann der Tunnel nichts # ansteuern, was nicht ohnehin im LAN sichtbar ist — insbesondere nichts im @@ -613,8 +619,11 @@ def _merge_devices(*lists) -> list[dict]: merged = {**d, **{k: v for k, v in cur.items() if v}} if order.get(d.get("via"), 0) >= order.get(cur.get("via"), 0): merged.update({k: v for k, v in d.items() if v}) - # DIAL-URL / mac aus beiden behalten - for key in ("dialAppUrl", "mac", "model", "manufacturer"): + # DIAL-URL / mac aus beiden behalten. isSatellite/satelliteId + # muessen mit: der Satellit findet sich selbst auch per mDNS, + # und dann duerfte die Kennzeichnung nicht verlorengehen. + for key in ("dialAppUrl", "mac", "model", "manufacturer", + "isSatellite", "satelliteId", "hostname"): merged[key] = d.get(key) or cur.get(key) or merged.get(key) by_ip[ip] = {k: v for k, v in merged.items() if v not in (None, "")} return list(by_ip.values()) + loose @@ -655,6 +664,36 @@ def _names_save() -> None: logger.warning("device_names.json schreiben fehlgeschlagen: %s", exc) +def _self_device() -> dict: + """Der Rechner, auf dem der Satellit SELBST laeuft, als normales Geraet. + + Er taucht in der Suche nicht auf (man findet sich nicht per mDNS/ARP + selbst), ist aber oft der interessanteste Host im Netz: hier laufen + typischerweise noch andere Dienste — ein FreeCAD-MCP, eine Web-App, ein + Dateiserver. Ohne diesen Eintrag waere ausgerechnet der Rechner unsichtbar, + auf dem ARIA schon eine Verbindung hat. + + `isSatellite` kennzeichnet ihn; ansonsten ist er ein Geraet wie jedes + andere und laesst sich genauso ansprechen. + """ + ip = (NET or {}).get("primary_ip") or "" + if not ip: + return {} + d = { + "ip": ip, + "mac": (NET or {}).get("mac") or "", + "via": "self", + "isSatellite": True, + "satelliteId": SATELLITE_ID, + "type": "host", + } + try: + d["hostname"] = socket.gethostname() + except Exception: + pass + return {k: v for k, v in d.items() if v not in (None, "")} + + def _name_key(dev: dict) -> str: mac = (dev.get("mac") or "").strip().lower() return mac if mac else (dev.get("ip") or "").strip() @@ -1778,7 +1817,9 @@ class Satellite: mdns = await loop.run_in_executor(None, _discover_mdns, DISCOVER_TIMEOUT_SEC) ssdp = await loop.run_in_executor(None, _discover_ssdp, DISCOVER_TIMEOUT_SEC) arp = await loop.run_in_executor(None, _discover_arp) - self._devices = _apply_names(_merge_devices(mdns, ssdp, arp)) + _self = [_self_device()] if LIST_SELF else [] + _self = [d for d in _self if d] + self._devices = _apply_names(_merge_devices(mdns, ssdp, arp, _self)) if SNMP_DISCOVERY: await self._enrich_snmp(self._devices) await self._enrich_adb(self._devices)