Forráskód Böngészése

Show table icons in table selector dropdown

- Add customLogo field to Table interface
- Fetch settings when discovering/adding tables to get custom logo
- Refresh remote tables in background to fetch their logos
- Replace wifi icons with table's actual icon/logo
- Add status dot overlay to indicate online/offline state

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
tuanchris 2 hete
szülő
commit
d4c9c3ee71

+ 22 - 8
frontend/src/components/TableSelector.tsx

@@ -27,8 +27,6 @@ import {
   Layers,
   Layers,
   Plus,
   Plus,
   Check,
   Check,
-  Wifi,
-  WifiOff,
   Pencil,
   Pencil,
   Trash2,
   Trash2,
 } from 'lucide-react'
 } from 'lucide-react'
@@ -154,12 +152,28 @@ export function TableSelector({ children }: TableSelectorProps) {
                   }`}
                   }`}
                   onClick={() => handleSelectTable(table)}
                   onClick={() => handleSelectTable(table)}
                 >
                 >
-                  {/* Status indicator */}
-                  {table.isOnline ? (
-                    <Wifi className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
-                  ) : (
-                    <WifiOff className="h-3.5 w-3.5 text-red-500 flex-shrink-0" />
-                  )}
+                  {/* Table icon with status indicator */}
+                  <div className="relative flex-shrink-0">
+                    <img
+                      src={
+                        table.customLogo
+                          ? `${table.isCurrent ? '' : table.url}/static/custom/${table.customLogo}`
+                          : `${table.isCurrent ? '' : table.url}/static/android-chrome-192x192.png`
+                      }
+                      alt={table.name}
+                      className="w-8 h-8 rounded-full object-cover"
+                      onError={(e) => {
+                        // Fallback to default icon if image fails to load
+                        (e.target as HTMLImageElement).src = '/static/android-chrome-192x192.png'
+                      }}
+                    />
+                    {/* Online status dot */}
+                    <span
+                      className={`absolute -bottom-0.5 -right-0.5 w-3 h-3 rounded-full border-2 border-popover ${
+                        table.isOnline ? 'bg-green-500' : 'bg-red-500'
+                      }`}
+                    />
+                  </div>
 
 
                   {/* Name and info */}
                   {/* Name and info */}
                   <div className="flex-1 min-w-0">
                   <div className="flex-1 min-w-0">

+ 61 - 12
frontend/src/contexts/TableContext.tsx

@@ -19,6 +19,7 @@ export interface Table {
   version?: string
   version?: string
   isOnline?: boolean
   isOnline?: boolean
   isCurrent?: boolean // True if this is the backend serving the frontend
   isCurrent?: boolean // True if this is the backend serving the frontend
+  customLogo?: string // Custom logo filename if set (e.g., "logo_abc123.png")
 }
 }
 
 
 interface TableContextType {
 interface TableContextType {
@@ -141,13 +142,19 @@ export function TableProvider({ children }: { children: React.ReactNode }) {
     setIsDiscovering(true)
     setIsDiscovering(true)
 
 
     try {
     try {
-      // Always fetch the current table's info
-      const infoResponse = await fetch('/api/table-info')
+      // Fetch table info and settings in parallel
+      const [infoResponse, settingsResponse] = await Promise.all([
+        fetch('/api/table-info'),
+        fetch('/api/settings').catch(() => null),
+      ])
+
       if (!infoResponse.ok) {
       if (!infoResponse.ok) {
         throw new Error('Failed to fetch table info')
         throw new Error('Failed to fetch table info')
       }
       }
 
 
       const info = await infoResponse.json()
       const info = await infoResponse.json()
+      const settings = settingsResponse?.ok ? await settingsResponse.json() : null
+
       const currentTable: Table = {
       const currentTable: Table = {
         id: info.id,
         id: info.id,
         name: info.name,
         name: info.name,
@@ -155,6 +162,7 @@ export function TableProvider({ children }: { children: React.ReactNode }) {
         version: info.version,
         version: info.version,
         isOnline: true,
         isOnline: true,
         isCurrent: true,
         isCurrent: true,
+        customLogo: settings?.app?.custom_logo || undefined,
       }
       }
 
 
       // Merge with existing tables
       // Merge with existing tables
@@ -197,6 +205,34 @@ export function TableProvider({ children }: { children: React.ReactNode }) {
       restoredActiveIdRef.current = null
       restoredActiveIdRef.current = null
 
 
       setLastDiscovery(new Date())
       setLastDiscovery(new Date())
+
+      // Refresh remote tables in the background to get their customLogo
+      // Use setTimeout to not block the main discovery flow
+      setTimeout(() => {
+        setTables(currentTables => {
+          const remoteTables = currentTables.filter(t => !t.isCurrent)
+          remoteTables.forEach(async (table) => {
+            try {
+              const [infoResponse, settingsResponse] = await Promise.all([
+                fetch(`${table.url}/api/table-info`, { signal: AbortSignal.timeout(3000) }),
+                fetch(`${table.url}/api/settings`, { signal: AbortSignal.timeout(3000) }).catch(() => null),
+              ])
+              const isOnline = infoResponse.ok
+              const settings = settingsResponse?.ok ? await settingsResponse.json() : null
+              const customLogo = settings?.app?.custom_logo || undefined
+
+              setTables(prev =>
+                prev.map(t => (t.id === table.id ? { ...t, isOnline, customLogo } : t))
+              )
+            } catch {
+              setTables(prev =>
+                prev.map(t => (t.id === table.id ? { ...t, isOnline: false } : t))
+              )
+            }
+          })
+          return currentTables // Return unchanged for now, updates happen in the async callbacks
+        })
+      }, 100)
     } catch (e) {
     } catch (e) {
       console.error('Table refresh failed:', e)
       console.error('Table refresh failed:', e)
     } finally {
     } finally {
@@ -215,13 +251,19 @@ export function TableProvider({ children }: { children: React.ReactNode }) {
         return null
         return null
       }
       }
 
 
-      // Fetch table info from the URL
-      const response = await fetch(`${normalizedUrl}/api/table-info`)
-      if (!response.ok) {
+      // Fetch table info and settings in parallel
+      const [infoResponse, settingsResponse] = await Promise.all([
+        fetch(`${normalizedUrl}/api/table-info`),
+        fetch(`${normalizedUrl}/api/settings`).catch(() => null),
+      ])
+
+      if (!infoResponse.ok) {
         throw new Error('Failed to fetch table info')
         throw new Error('Failed to fetch table info')
       }
       }
 
 
-      const info = await response.json()
+      const info = await infoResponse.json()
+      const settings = settingsResponse?.ok ? await settingsResponse.json() : null
+
       const newTable: Table = {
       const newTable: Table = {
         id: info.id,
         id: info.id,
         name: name || info.name,
         name: name || info.name,
@@ -229,6 +271,7 @@ export function TableProvider({ children }: { children: React.ReactNode }) {
         version: info.version,
         version: info.version,
         isOnline: true,
         isOnline: true,
         isCurrent: false,
         isCurrent: false,
+        customLogo: settings?.app?.custom_logo || undefined,
       }
       }
 
 
       setTables(prev => [...prev, newTable])
       setTables(prev => [...prev, newTable])
@@ -283,17 +326,23 @@ export function TableProvider({ children }: { children: React.ReactNode }) {
     }
     }
   }, [tables, activeTable])
   }, [tables, activeTable])
 
 
-  // Check if a table is online
+  // Check if a table is online and update its info (including custom logo)
   const refreshTableStatus = useCallback(async (table: Table): Promise<boolean> => {
   const refreshTableStatus = useCallback(async (table: Table): Promise<boolean> => {
     try {
     try {
       const baseUrl = table.isCurrent ? '' : table.url
       const baseUrl = table.isCurrent ? '' : table.url
-      const response = await fetch(`${baseUrl}/api/table-info`, {
-        signal: AbortSignal.timeout(3000),
-      })
-      const isOnline = response.ok
+
+      // Fetch table info and settings in parallel
+      const [infoResponse, settingsResponse] = await Promise.all([
+        fetch(`${baseUrl}/api/table-info`, { signal: AbortSignal.timeout(3000) }),
+        fetch(`${baseUrl}/api/settings`, { signal: AbortSignal.timeout(3000) }).catch(() => null),
+      ])
+
+      const isOnline = infoResponse.ok
+      const settings = settingsResponse?.ok ? await settingsResponse.json() : null
+      const customLogo = settings?.app?.custom_logo || undefined
 
 
       setTables(prev =>
       setTables(prev =>
-        prev.map(t => (t.id === table.id ? { ...t, isOnline } : t))
+        prev.map(t => (t.id === table.id ? { ...t, isOnline, customLogo } : t))
       )
       )
 
 
       return isOnline
       return isOnline