generated from Dicken/dickendock
Hersteller-Auto-Erkennung ueber MAC (verifiziert), Geraete-Tabelle Hersteller/Modell-Spalten, Dropdown-Klick-Bug, Kategorie-Farbe als Hintergrund
This commit is contained in:
@@ -97,6 +97,7 @@ export function HomePage() {
|
||||
const { data: readLaterItems } = useReadLater();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const searchContainerRef = useRef<HTMLDivElement>(null);
|
||||
const resultsContainerRef = useRef<HTMLDivElement>(null);
|
||||
const queryClient = useQueryClient();
|
||||
const isSearching = query.trim().length > 0;
|
||||
const isLoading = servicesLoading || bookmarksLoading;
|
||||
@@ -175,11 +176,16 @@ export function HomePage() {
|
||||
}, [isSearching]);
|
||||
|
||||
// Klick außerhalb von Suchfeld+Trefferliste klappt das Dropdown wieder ein
|
||||
// (Desktop-Verhalten, Text bleibt erhalten).
|
||||
// (Desktop-Verhalten, Text bleibt erhalten). Trefferliste liegt als
|
||||
// eigener Scroll-Container außerhalb von searchContainerRef, daher beide
|
||||
// prüfen - sonst schließt ein Klick auf einen Treffer das Dropdown, bevor
|
||||
// dessen eigener onClick überhaupt feuert.
|
||||
useEffect(() => {
|
||||
function onPointerDown(e: MouseEvent) {
|
||||
if (!searchContainerRef.current) return;
|
||||
if (!searchContainerRef.current.contains(e.target as Node)) {
|
||||
const target = e.target as Node;
|
||||
const insideSearch = searchContainerRef.current?.contains(target);
|
||||
const insideResults = resultsContainerRef.current?.contains(target);
|
||||
if (!insideSearch && !insideResults) {
|
||||
setResultsVisible(false);
|
||||
}
|
||||
}
|
||||
@@ -340,7 +346,7 @@ export function HomePage() {
|
||||
|
||||
{/* Scrollender Bereich: NUR die Trefferliste scrollt, nicht die ganze Seite */}
|
||||
{isSearching && resultsVisible ? (
|
||||
<div className="min-h-0 flex-1 px-6 pb-4">
|
||||
<div ref={resultsContainerRef} className="min-h-0 flex-1 px-6 pb-4">
|
||||
<div className="mx-auto h-full max-w-xl">
|
||||
{isLoading ? (
|
||||
<p className="text-center text-sm text-black/40 dark:text-white/40">Lade …</p>
|
||||
|
||||
@@ -86,7 +86,7 @@ function EditDeviceForm({ device, onDone }: { device: DeviceWithServices; onDone
|
||||
|
||||
return (
|
||||
<tr className="border-b border-black/5 bg-black/[0.02] last:border-0 dark:border-white/5 dark:bg-white/5">
|
||||
<td colSpan={6} className="px-4 py-3">
|
||||
<td colSpan={8} className="px-4 py-3">
|
||||
<div className="flex flex-wrap items-end gap-3">
|
||||
<div>
|
||||
<label className="mb-1 block text-xs text-black/50 dark:text-white/50">Hostname</label>
|
||||
@@ -246,6 +246,8 @@ function DeviceRow({ device }: { device: DeviceWithServices }) {
|
||||
<td className="px-4 py-3 font-mono text-xs text-black/40 dark:text-white/40">
|
||||
{device.mac ?? "–"}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-black/60 dark:text-white/60">{device.manufacturer ?? "–"}</td>
|
||||
<td className="px-4 py-3 text-black/60 dark:text-white/60">{device.model ?? "–"}</td>
|
||||
<td className="px-4 py-3">
|
||||
<span
|
||||
className={`inline-flex items-center gap-1.5 text-xs ${
|
||||
@@ -293,7 +295,7 @@ function DeviceRow({ device }: { device: DeviceWithServices }) {
|
||||
|
||||
{(scanMessage || staleServices.length > 0) && (
|
||||
<tr className="border-b border-black/5 dark:border-white/5">
|
||||
<td colSpan={6} className="px-4 pb-3">
|
||||
<td colSpan={8} className="px-4 pb-3">
|
||||
{scanMessage ? (
|
||||
<p className="text-xs text-black/40 dark:text-white/40">{scanMessage}</p>
|
||||
) : null}
|
||||
@@ -306,7 +308,7 @@ function DeviceRow({ device }: { device: DeviceWithServices }) {
|
||||
|
||||
{expanded && (
|
||||
<tr className="border-b border-black/5 bg-black/[0.015] dark:border-white/5 dark:bg-white/[0.02]">
|
||||
<td colSpan={6} className="px-4 py-3">
|
||||
<td colSpan={8} className="px-4 py-3">
|
||||
{device.services.length === 0 ? (
|
||||
<p className="text-xs text-black/40 dark:text-white/40">Keine Dienste auf diesem Gerät.</p>
|
||||
) : (
|
||||
@@ -393,7 +395,7 @@ function AddDeviceForm() {
|
||||
);
|
||||
}
|
||||
|
||||
type SortColumn = "hostname" | "ip" | "mac" | "online" | "services" | null;
|
||||
type SortColumn = "hostname" | "ip" | "mac" | "manufacturer" | "model" | "online" | "services" | null;
|
||||
|
||||
function SortableHeader({
|
||||
label,
|
||||
@@ -444,6 +446,12 @@ export function DevicesPage() {
|
||||
case "mac":
|
||||
cmp = (a.mac ?? "").localeCompare(b.mac ?? "");
|
||||
break;
|
||||
case "manufacturer":
|
||||
cmp = (a.manufacturer ?? "").localeCompare(b.manufacturer ?? "");
|
||||
break;
|
||||
case "model":
|
||||
cmp = (a.model ?? "").localeCompare(b.model ?? "");
|
||||
break;
|
||||
case "online":
|
||||
cmp = Number(a.online) - Number(b.online);
|
||||
break;
|
||||
@@ -483,13 +491,15 @@ export function DevicesPage() {
|
||||
) : list.length > 0 ? (
|
||||
<div className="overflow-hidden rounded-2xl border border-black/10 dark:border-white/10">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[760px] text-sm">
|
||||
<table className="w-full min-w-[980px] text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-black/10 bg-black/[0.02] text-left text-xs
|
||||
uppercase tracking-wide text-black/40 dark:border-white/10 dark:bg-white/5 dark:text-white/40">
|
||||
<SortableHeader label="Gerät" column="hostname" activeColumn={sortColumn} direction={sortDirection} onClick={handleHeaderClick} />
|
||||
<SortableHeader label="IP" column="ip" activeColumn={sortColumn} direction={sortDirection} onClick={handleHeaderClick} />
|
||||
<SortableHeader label="MAC" column="mac" activeColumn={sortColumn} direction={sortDirection} onClick={handleHeaderClick} />
|
||||
<SortableHeader label="Hersteller" column="manufacturer" activeColumn={sortColumn} direction={sortDirection} onClick={handleHeaderClick} />
|
||||
<SortableHeader label="Modell" column="model" activeColumn={sortColumn} direction={sortDirection} onClick={handleHeaderClick} />
|
||||
<SortableHeader label="Status" column="online" activeColumn={sortColumn} direction={sortDirection} onClick={handleHeaderClick} />
|
||||
<SortableHeader label="Dienste" column="services" activeColumn={sortColumn} direction={sortDirection} onClick={handleHeaderClick} />
|
||||
<th className="px-4 py-2" />
|
||||
|
||||
Reference in New Issue
Block a user