mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-26 11:49:16 +00:00
feat(fleet): refine the Fleet Overview toolbar (#1376)
* feat(fleet): tidy the Overview toolbar and shorten tab labels - Move the Add node button into the Overview toolbar beside the Grid/Topology toggle so it sits with the view it acts on instead of showing on every Fleet tab. It stays admin-only. - Collapse the node search to an icon button that expands to the full input on click and collapses again on blur once the query is empty, reclaiming toolbar width. An active query keeps it open. - Match the sort-direction toggle and the sort dropdown to the outlined dark fill of the Filters button for a consistent toolbar row. - Swap the Check Updates icon to the refresh-with-dot glyph. - Rename two tabs: Fleet Actions to Actions, Dependencies to Map (display labels only; internal keys unchanged). Update the feature docs to the new tab labels. * feat(fleet): move Check Updates into the Overview tab toolbar Check Updates is an Overview-specific action, but it lived in the shared Fleet header toolbar that renders across every tab. Move it into the Overview tab's own toolbar, to the right of the Add node button, so it only appears where it applies. The relocated button now spins and disables while a check is in flight, matching the Refresh button's loading feedback. The shared header keeps Refresh and Export Dossier.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
RefreshCw, RefreshCcwDot, Camera, FileDown,
|
||||
RefreshCw, Camera, FileDown,
|
||||
Network, SlidersHorizontal,
|
||||
Send, KeyRound, ArrowLeftRight, Wrench, Workflow,
|
||||
} from 'lucide-react';
|
||||
@@ -133,15 +133,6 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) {
|
||||
</TabsHighlight>
|
||||
</TabsList>
|
||||
<div className="flex items-center gap-2 max-md:w-full max-md:flex-wrap">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={updateStatus.checkUpdates}
|
||||
className="gap-2"
|
||||
>
|
||||
<RefreshCcwDot className="w-4 h-4" />
|
||||
Check Updates
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -195,6 +186,8 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) {
|
||||
onEditNode={isAdmin ? openEdit : undefined}
|
||||
onDeleteNode={isAdmin ? openDelete : undefined}
|
||||
onAddNode={isAdmin ? openCreate : undefined}
|
||||
onCheckUpdates={updateStatus.checkUpdates}
|
||||
checkingUpdates={updateStatus.checkingUpdates}
|
||||
topologyMode={topology.prefs.mode}
|
||||
onTopologyModeChange={topology.setMode}
|
||||
topologyPositions={topology.prefs.positions}
|
||||
|
||||
@@ -40,6 +40,8 @@ interface OverviewTabProps {
|
||||
topologyPositions: SavedPositions;
|
||||
onTopologyPositionsChange: (positions: SavedPositions) => void;
|
||||
onAddNode?: () => void;
|
||||
onCheckUpdates?: () => void;
|
||||
checkingUpdates?: boolean;
|
||||
}
|
||||
|
||||
export function OverviewTab({
|
||||
@@ -73,6 +75,8 @@ export function OverviewTab({
|
||||
topologyPositions,
|
||||
onTopologyPositionsChange,
|
||||
onAddNode,
|
||||
onCheckUpdates,
|
||||
checkingUpdates,
|
||||
}: OverviewTabProps) {
|
||||
return (
|
||||
<>
|
||||
@@ -116,6 +120,8 @@ export function OverviewTab({
|
||||
onLabelFiltersChange={onLabelFiltersChange}
|
||||
onClearFilters={onClearFilters}
|
||||
onAddNode={onAddNode}
|
||||
onCheckUpdates={onCheckUpdates}
|
||||
checkingUpdates={checkingUpdates}
|
||||
/>
|
||||
|
||||
{viewMode === 'topology' && processedNodes.length > 0 ? (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Search, ArrowUpDown, AlertTriangle, Play, Square,
|
||||
LayoutGrid, Network, SlidersHorizontal, Plus,
|
||||
LayoutGrid, Network, SlidersHorizontal, Plus, RefreshCcwDot,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
@@ -50,6 +50,8 @@ interface OverviewToolbarProps {
|
||||
onLabelFiltersChange: (filters: Set<string>) => void;
|
||||
onClearFilters: () => void;
|
||||
onAddNode?: () => void;
|
||||
onCheckUpdates?: () => void;
|
||||
checkingUpdates?: boolean;
|
||||
}
|
||||
|
||||
export function OverviewToolbar({
|
||||
@@ -64,6 +66,8 @@ export function OverviewToolbar({
|
||||
onLabelFiltersChange,
|
||||
onClearFilters,
|
||||
onAddNode,
|
||||
onCheckUpdates,
|
||||
checkingUpdates,
|
||||
}: OverviewToolbarProps) {
|
||||
const showGridControls = viewMode === 'grid';
|
||||
const activeFilterCount =
|
||||
@@ -257,6 +261,19 @@ export function OverviewToolbar({
|
||||
Add node
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{onCheckUpdates && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 shrink-0 h-9"
|
||||
onClick={onCheckUpdates}
|
||||
disabled={checkingUpdates}
|
||||
>
|
||||
<RefreshCcwDot className={`w-4 h-4 ${checkingUpdates ? 'animate-spin' : ''}`} />
|
||||
Check Updates
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -93,4 +93,21 @@ describe('OverviewToolbar', () => {
|
||||
render(<OverviewToolbar {...props()} />);
|
||||
expect(screen.queryByRole('button', { name: 'Add node' })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the Check Updates button and fires onCheckUpdates when provided', () => {
|
||||
const onCheckUpdates = vi.fn();
|
||||
render(<OverviewToolbar {...props({ onCheckUpdates })} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /Check Updates/ }));
|
||||
expect(onCheckUpdates).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('omits the Check Updates button when onCheckUpdates is not provided', () => {
|
||||
render(<OverviewToolbar {...props()} />);
|
||||
expect(screen.queryByRole('button', { name: /Check Updates/ })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables the Check Updates button while a check is in flight', () => {
|
||||
render(<OverviewToolbar {...props({ onCheckUpdates: vi.fn(), checkingUpdates: true })} />);
|
||||
expect(screen.getByRole('button', { name: /Check Updates/ })).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user