From b0d382b2979a2d1f0fbc4976e06db8011bc7bfd8 Mon Sep 17 00:00:00 2001 From: Trezy Date: Sat, 23 May 2026 01:16:18 +0000 Subject: [PATCH] feat: recommend restart when backfill concurrency settings dont match existing connection pool size Signed-off-by: Trezy --- web/src/app/dashboard/layout.tsx | 25 ++++++++++++++----------- web/src/components/app-sidebar.tsx | 22 ++++++++++++++++++++++ web/src/components/site-header.tsx | 67 +++++++++++++++++++++++++++++++++++++++++++++++++------------------ web/src/lib/api.ts | 1 + web/src/lib/restart-context.tsx | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 5 file(s) changed, 133 insertion(s)(+), 29 deletion(s)(-) diff --git a/web/src/app/dashboard/layout.tsx b/web/src/app/dashboard/layout.tsx --- a/web/src/app/dashboard/layout.tsx +++ b/web/src/app/dashboard/layout.tsx @@ -7,6 +7,7 @@ import { useAuth } from "@/lib/auth-context" import { useConfig } from "@/lib/config-context" import { AppSidebar } from "@/components/app-sidebar" import { PluginUpdateProvider } from "@/components/plugin-update-provider" +import { RestartProvider } from "@/lib/restart-context" import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar" import { Toaster } from "@/components/ui/sonner" @@ -33,17 +34,19 @@ if (!did) return null return ( - - - {children} - + + + + {children} + + ) diff --git a/web/src/components/app-sidebar.tsx b/web/src/components/app-sidebar.tsx --- a/web/src/components/app-sidebar.tsx +++ b/web/src/components/app-sidebar.tsx @@ -29,6 +29,8 @@ import { useAuth } from "@/lib/auth-context"; import { useConfig } from "@/lib/config-context"; import { useCurrentUser } from "@/hooks/use-current-user"; import { usePluginUpdates } from "@/components/plugin-update-provider"; +import { useRestart } from "@/lib/restart-context"; +import { getDbInfo } from "@/lib/api"; import { Scroller } from "@/components/ui/scroller"; import { Sidebar, @@ -138,6 +140,7 @@ const { logout } = useAuth(); const { app_name, logo_url } = useConfig(); const { hasPermission } = useCurrentUser(); const { hasUpdates } = usePluginUpdates(); + const { addReason, removeReason } = useRestart(); const [deadLetterCount, setDeadLetterCount] = useState(0); @@ -159,6 +162,25 @@ cancelled = true; clearInterval(interval); }; }, []); + + useEffect(() => { + if (!hasPermission("settings:manage")) return; + let cancelled = false; + getDbInfo() + .then((info) => { + if (!cancelled) { + if (info.restart_recommended) { + addReason("backfill-pool", "Backfill pool is undersized for current concurrency settings."); + } else { + removeReason("backfill-pool"); + } + } + }) + .catch(() => {}); + return () => { + cancelled = true; + }; + }, [hasPermission, addReason, removeReason]); function filterByPermission(items: NavItem[]) { return items.filter( diff --git a/web/src/components/site-header.tsx b/web/src/components/site-header.tsx --- a/web/src/components/site-header.tsx +++ b/web/src/components/site-header.tsx @@ -1,8 +1,11 @@ "use client" +import { useState } from "react" import { IconArrowLeft } from "@tabler/icons-react" +import { AlertTriangle, ChevronDown } from "lucide-react" import Link from "next/link" +import { useRestart } from "@/lib/restart-context" import { Button } from "@/components/ui/button" import { Separator } from "@/components/ui/separator" import { SidebarTrigger } from "@/components/ui/sidebar" @@ -15,27 +18,55 @@ }: { title: string backHref?: string }) { + const { reasons } = useRestart() + const [expanded, setExpanded] = useState(false) + return ( -
-
- - - {backHref && ( - - )} -

{title}

-
- +
+
+
+ + + {backHref && ( + + )} +

{title}

+
+ +
+ {reasons.length > 0 && ( +
+ + {expanded && ( +
    + {reasons.map((reason, i) => ( +
  • + + {reason} +
  • + ))} +
+ )} +
+ )}
) } diff --git a/web/src/lib/api.ts b/web/src/lib/api.ts --- a/web/src/lib/api.ts +++ b/web/src/lib/api.ts @@ -384,6 +384,7 @@ backend: "sqlite" | "postgres"; server_max_connections: number | null; main_pool_size: number; backfill_pool_size: number; + restart_recommended: boolean; }; export function getDbInfo() { diff --git a/web/src/lib/restart-context.tsx b/web/src/lib/restart-context.tsx new file mode 100644 --- /dev/null +++ b/web/src/lib/restart-context.tsx @@ -0,0 +1,47 @@ +"use client"; + +import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react"; + +interface RestartContextType { + reasons: string[]; + addReason: (key: string, reason: string) => void; + removeReason: (key: string) => void; +} + +const RestartContext = createContext({ + reasons: [], + addReason: () => {}, + removeReason: () => {}, +}); + +export function RestartProvider({ children }: { children: React.ReactNode }) { + const [reasonMap, setReasonMap] = useState>({}); + const reasonMapRef = useRef(reasonMap); + reasonMapRef.current = reasonMap; + + const addReason = useCallback((key: string, reason: string) => { + if (reasonMapRef.current[key] === reason) return; + setReasonMap((prev) => ({ ...prev, [key]: reason })); + }, []); + + const removeReason = useCallback((key: string) => { + setReasonMap((prev) => { + if (!(key in prev)) return prev; + const next = { ...prev }; + delete next[key]; + return next; + }); + }, []); + + const reasons = useMemo(() => Object.values(reasonMap), [reasonMap]); + + return ( + + {children} + + ); +} + +export function useRestart() { + return useContext(RestartContext); +} -- tangled.sh