Files
sencho/frontend/src/components/HomeDashboard.tsx
T
Anso bb35c1bc92 feat: add sidebar update indicator toggle and Stack Health badge (#1570)
* feat: add sidebar update indicator toggle and Stack Health badge

- Add image_update_sidebar_indicators setting (default off, node-scoped)
- Gate the Updates filter chip and sidebar status indicators on the setting
- Add "Update available" badge to Stack Health table (always visible)
- Extend ImageUpdateStatus with sidebarIndicators boolean
- Poll /api/image-updates/status alongside /detail in useImageUpdates
- React to SENCHO_SETTINGS_CHANGED for instant toggle propagation
- Reset sidebar state on node switch; generation-guard stale responses
- Disable toggle when status is null (loading) or field is absent (old node)
- Wire stackUpdates through ViewRouter → HomeDashboard → StackHealthTable
- Update settings registry, operator docs, and sidebar/dashboard docs

* fix: guard against stale node renders, memo drift, and cross-node error toasts

- Track owning node ID in useImageUpdates state so React never renders
  node B with node A's data before the passive effect resets (P2)
- Replace incorrect stackUpdates dependency with sidebarStackUpdates in
  chipFilteredFiles useMemo (P3)
- Guard the error toast in handleSidebarIndicatorsChange so a stale PATCH
  failure from node A does not surface while viewing node B (P3)

* fix: default sidebar update indicators to on (opt-out)

The sidebar indicators are a safe convenience that most users want.
Switching the default from off to on matches the opt-out convention
used by prune_on_update, reclaim_hero, and health_gate_enabled.
2026-07-05 02:52:17 -04:00

70 lines
2.1 KiB
TypeScript

import { useNodes } from '@/context/NodeContext';
import type { NotificationItem } from './dashboard/types';
import type { SectionId } from './settings/types';
import type { StackUpdateInfo } from '@/types/imageUpdates';
import {
HealthStatusBar,
ResourceGauges,
StackHealthTable,
ConfigurationStatus,
RecentAlerts,
useDashboardData,
} from './dashboard';
import { DashboardActivityCard } from './dashboard/DashboardActivityCard';
interface HomeDashboardProps {
onNavigateToStack?: (stackFile: string) => void;
onOpenSettingsSection?: (section: SectionId) => void;
notifications: NotificationItem[];
onClearNotifications: () => void | Promise<void>;
stackUpdates?: Record<string, StackUpdateInfo>;
}
const NOOP = () => {};
export default function HomeDashboard({ onNavigateToStack, onOpenSettingsSection, notifications, onClearNotifications, stackUpdates = {} }: HomeDashboardProps) {
const { activeNode, nodes } = useNodes();
const data = useDashboardData();
const activeNodeName = activeNode?.name || 'Local';
return (
<div className="flex-1 p-6 space-y-4">
<HealthStatusBar
stats={data.stats}
systemStats={data.systemStats}
notifications={notifications}
activeNodeName={activeNodeName}
nodeCount={data.nodeCount}
lastSyncAt={data.lastSyncAt}
metricsStale={data.metricsStale}
/>
<ResourceGauges
systemStats={data.systemStats}
cpuHistory={data.cpuHistory}
netHistory={data.netHistory}
historyEndAt={data.historyEndAt}
/>
<StackHealthTable
stackStatuses={data.stackStatuses}
metrics={data.metrics}
stackCpuSeries={data.stackCpuSeries}
onNavigateToStack={onNavigateToStack ?? NOOP}
stackUpdates={stackUpdates}
/>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<ConfigurationStatus onOpenSection={onOpenSettingsSection} />
<DashboardActivityCard />
</div>
<RecentAlerts
notifications={notifications}
nodes={nodes}
onCleared={onClearNotifications}
/>
</div>
);
}