round19: Startseite aufgeraeumt, Favicon-Fallback ueber Ports, Favicon-Picker, Live-Status per Ping

This commit is contained in:
2026-07-23 18:10:03 +02:00
parent bae9e48fc1
commit ec49534b60
16 changed files with 393 additions and 51 deletions

View File

@@ -77,9 +77,15 @@ function extractFaviconUrl(html: string, baseUrl: string): string | undefined {
/<link[^>]+rel=["'](?:shortcut icon|icon)["'][^>]*href=["']([^"']+)["']/i
);
const href = match?.[1];
// Kein <link rel="icon"> im HTML gefunden -> hier NICHT mehr blind auf
// "/favicon.ico" raten (das täuscht ein bestätigtes Favicon vor, das oft
// gar nicht existiert - 404). Der Aufrufer (scanDeviceServices) entscheidet
// die Fallback-Reihenfolge: erst ein Favicon eines anderen Dienstes auf
// demselben Gerät übernehmen, erst danach selbst raten.
if (!href) return undefined;
try {
return new URL(href ?? "/favicon.ico", baseUrl).toString();
return new URL(href, baseUrl).toString();
} catch {
return undefined;
}

View File

@@ -136,5 +136,17 @@ export async function scanDeviceServices(
});
}
// Fallback-Reihenfolge für Dienste ohne bestätigtes Favicon (kein
// <link rel="icon"> im HTML gefunden): zuerst das Favicon eines ANDEREN
// Dienstes auf demselben Gerät übernehmen (typischerweise dieselbe
// Software/dasselbe Gerät, das Icon passt meist trotzdem) - erst wenn auch
// das fehlt, wird als letzter Ausweg "/favicon.ico" am eigenen Port
// geraten (kann ins Leere laufen, ist aber besser als gar kein Versuch).
const firstSiblingFavicon = found.find((s) => s.favicon)?.favicon;
for (const service of found) {
if (service.favicon) continue;
service.favicon = firstSiblingFavicon ?? `${service.url}/favicon.ico`;
}
return { services: found, suggestedHostname };
}

View File

@@ -0,0 +1,23 @@
import { exec } from "node:child_process";
import { platform } from "node:os";
/**
* Prüft per System-Ping (ICMP), ob eine IP erreichbar ist. Bewusst NICHT über
* einen Port-Connect (wie isPortOpen in ports.ts) - ein Gerät kann online
* sein, ohne einen der bekannten Ports offen zu haben (z. B. ein Drucker
* ohne Web-UI), ein echter Ping ist hier das treffendere Signal.
*
* Nutzt den System-ping-Befehl statt eines rohen ICMP-Sockets, da Node dafür
* root-Rechte bräuchte - für einen einzelnen Zwischencheck reicht das völlig.
* Timeout: 1 Sekunde, ein einzelnes Paket.
*/
export function pingHost(ip: string): Promise<boolean> {
const isWindows = platform() === "win32";
const command = isWindows ? `ping -n 1 -w 1000 ${ip}` : `ping -c 1 -W 1 ${ip}`;
return new Promise((resolve) => {
exec(command, { timeout: 2000 }, (error) => {
resolve(!error);
});
});
}