generated from Dicken/dickendock
round22: Startseite kompakter, Ping-Zeitstempel-Bug behoben, echte Favicon-Deduplizierung per Bildinhalt, automatischer Favicon-Abgleich aus Icon-Datenbank + Nachruest-Button
This commit is contained in:
@@ -334,7 +334,7 @@ export function HomePage() {
|
||||
dark:bg-white/[0.02]"
|
||||
>
|
||||
{hasFavorites ? (
|
||||
<div className="flex flex-col gap-4 p-4">
|
||||
<div className="flex flex-col gap-3 p-3">
|
||||
{favoriteServices.length > 0 ? (
|
||||
<FavoritesBar
|
||||
items={favoriteServices}
|
||||
@@ -363,7 +363,7 @@ export function HomePage() {
|
||||
) : null}
|
||||
|
||||
{recentVisits && recentVisits.length > 0 ? (
|
||||
<div className="p-4">
|
||||
<div className="p-3">
|
||||
<FavoritesBar
|
||||
items={recentVisits}
|
||||
label="Zuletzt besucht"
|
||||
@@ -376,7 +376,7 @@ export function HomePage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="p-4">
|
||||
<div className="p-3">
|
||||
<div className="mb-2.5 flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-medium uppercase tracking-wide text-black/35 dark:text-white/35">
|
||||
Später lesen
|
||||
@@ -384,23 +384,23 @@ export function HomePage() {
|
||||
<ReadLaterBox />
|
||||
</div>
|
||||
{hasReadLaterChips ? (
|
||||
<div className="flex flex-wrap gap-2.5">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{readLaterItems!.slice(0, readLaterLimit).map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
onClick={() => window.open(item.url, "_blank", "noopener,noreferrer")}
|
||||
title={item.displayName}
|
||||
className="group flex w-16 flex-col items-center gap-1.5 rounded-xl p-1.5
|
||||
className="group flex w-14 flex-col items-center gap-1 rounded-xl p-1
|
||||
transition-colors hover:bg-black/[0.04] dark:hover:bg-white/[0.06]"
|
||||
>
|
||||
<span
|
||||
className="flex h-11 w-11 items-center justify-center rounded-xl border
|
||||
className="flex h-9 w-9 items-center justify-center rounded-xl border
|
||||
border-black/10 bg-white/80 shadow-sm transition-transform
|
||||
group-hover:scale-105 dark:border-white/10 dark:bg-white/5"
|
||||
>
|
||||
<Favicon src={item.favicon} fallbackLetter={item.displayName} size="md" />
|
||||
</span>
|
||||
<span className="w-full truncate text-center text-[11px] leading-tight text-black/55 dark:text-white/55">
|
||||
<span className="w-full truncate text-center text-[10px] leading-tight text-black/55 dark:text-white/55">
|
||||
{item.displayName}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -503,6 +503,46 @@ function SortableHeader({
|
||||
);
|
||||
}
|
||||
|
||||
function BackfillFaviconsButton() {
|
||||
const queryClient = useQueryClient();
|
||||
const [message, setMessage] = useState<string | null>(null);
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async () => {
|
||||
const res = await fetch("/api/services/backfill-favicons", { method: "POST" });
|
||||
if (!res.ok) throw new Error(`Fehlgeschlagen (HTTP ${res.status})`);
|
||||
return res.json() as Promise<{ checked: number; updated: number }>;
|
||||
},
|
||||
onSuccess: (result) => {
|
||||
setMessage(
|
||||
result.updated > 0
|
||||
? `${result.updated} von ${result.checked} Diensten mit einem passenden Favicon aus der Icon-Datenbank ergänzt.`
|
||||
: `Keine passenden Treffer gefunden (${result.checked} Dienste ohne Favicon geprüft).`
|
||||
);
|
||||
queryClient.invalidateQueries({ queryKey: ["services"] });
|
||||
},
|
||||
onError: () => setMessage("Fehlgeschlagen - ist die Icon-Datenbank erreichbar (Internetzugang)?"),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2 pb-2">
|
||||
{message ? <span className="text-xs text-black/40 dark:text-white/40">{message}</span> : null}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
setMessage(null);
|
||||
mutation.mutate();
|
||||
}}
|
||||
disabled={mutation.isPending}
|
||||
title="Für alle Dienste ohne Favicon einen passenden Treffer aus der Icon-Datenbank suchen"
|
||||
>
|
||||
{mutation.isPending ? "Suche …" : "Fehlende Favicons ergänzen"}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ServicesPage() {
|
||||
const { data: services, isLoading, isError } = useServices();
|
||||
const { data: devices } = useDevices();
|
||||
@@ -633,27 +673,30 @@ export function ServicesPage() {
|
||||
description="Spaltenköpfe anklickbar zum Sortieren; Drag & Drop (⠿⠿) nur in der Standard-Reihenfolge. Name, Kategorie, Alias, IP/Port/Protokoll und Reihenfolge bleiben bei erneuten Scans erhalten."
|
||||
/>
|
||||
|
||||
<div className="mb-4 flex gap-1 border-b border-black/10 dark:border-white/10">
|
||||
<button
|
||||
onClick={() => setActiveTab("visible")}
|
||||
className={`px-3 py-2 text-sm font-medium ${
|
||||
activeTab === "visible"
|
||||
? "border-b-2 border-black text-black dark:border-white dark:text-white"
|
||||
: "text-black/40 hover:text-black/70 dark:text-white/40 dark:hover:text-white/70"
|
||||
}`}
|
||||
>
|
||||
Sichtbar ({visibleCount})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("hidden")}
|
||||
className={`px-3 py-2 text-sm font-medium ${
|
||||
activeTab === "hidden"
|
||||
? "border-b-2 border-black text-black dark:border-white dark:text-white"
|
||||
: "text-black/40 hover:text-black/70 dark:text-white/40 dark:hover:text-white/70"
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEyeSlash} /> Ausgeblendet ({hiddenCount})
|
||||
</button>
|
||||
<div className="mb-4 flex items-center justify-between gap-2 border-b border-black/10 dark:border-white/10">
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => setActiveTab("visible")}
|
||||
className={`px-3 py-2 text-sm font-medium ${
|
||||
activeTab === "visible"
|
||||
? "border-b-2 border-black text-black dark:border-white dark:text-white"
|
||||
: "text-black/40 hover:text-black/70 dark:text-white/40 dark:hover:text-white/70"
|
||||
}`}
|
||||
>
|
||||
Sichtbar ({visibleCount})
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("hidden")}
|
||||
className={`px-3 py-2 text-sm font-medium ${
|
||||
activeTab === "hidden"
|
||||
? "border-b-2 border-black text-black dark:border-white dark:text-white"
|
||||
: "text-black/40 hover:text-black/70 dark:text-white/40 dark:hover:text-white/70"
|
||||
}`}
|
||||
>
|
||||
<FontAwesomeIcon icon={faEyeSlash} /> Ausgeblendet ({hiddenCount})
|
||||
</button>
|
||||
</div>
|
||||
<BackfillFaviconsButton />
|
||||
</div>
|
||||
|
||||
{sortColumn ? (
|
||||
|
||||
Reference in New Issue
Block a user