diff --git a/frontend/src/pages/AgentFleet.jsx b/frontend/src/pages/AgentFleet.jsx
index 4692219f..ac8a0869 100644
--- a/frontend/src/pages/AgentFleet.jsx
+++ b/frontend/src/pages/AgentFleet.jsx
@@ -22,10 +22,8 @@ import api from '../services/api';
import { useToast } from '../contexts/useToast.js';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
-import { Badge } from '@/components/ui/badge';
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select';
-import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import { MetricCard, KpiBand, Pill, Gauge, DataTable, DataTableFooter, statusKind } from '@/components/ds';
+import { MetricCard, KpiBand, Pill, Gauge, SegControl, DataTable, DataTableFooter, statusKind } from '@/components/ds';
import { useTranslation } from 'react-i18next';
import EmptyState from '../components/EmptyState';
import ErrorState from '../components/ErrorState';
@@ -105,15 +103,29 @@ const AgentFleet = () => {
fetchData();
}, [fetchData]);
- // Publish the Refresh button to the shared tab-group top bar; re-registers
- // on `loading` so the spinner/disabled state stays in sync.
- useTopbarActions(() =>
- ,
- [loading]
- );
+ // Publish the view switcher + Refresh to the shared tab-group top bar;
+ // re-registers on the values the node reads so spinner/counts stay in sync.
+ useTopbarActions(() => (
+ <>
+ , label: t('app.agentFleet.dashboard', 'Dashboard') },
+ { value: 'versions', icon: , label: t('app.agentFleet.versions', 'Versions') },
+ { value: 'rollouts', icon: , label: t('app.agentFleet.rollouts', 'Rollouts') },
+ { value: 'queue', icon: , label: t('app.agentFleet.commandQueue', 'Command queue'), count: queuedCommands.length || undefined },
+ { value: 'discovery', icon: , label: t('app.agentFleet.discovery', 'Discovery') },
+ { value: 'approvals', icon: , label: t('app.agentFleet.approvals', 'Approvals'), count: pendingServers.length || undefined },
+ ]}
+ />
+
+ >
+ ), [loading, activeTab, queuedCommands.length, pendingServers.length]);
const startDiscovery = async () => {
@@ -462,30 +474,6 @@ const AgentFleet = () => {
return (
-
-
- {[
- { key: 'dashboard', icon: Activity, labelKey: 'app.agentFleet.dashboard', label: 'Dashboard' },
- { key: 'versions', icon: Package, labelKey: 'app.agentFleet.versions', label: 'Versions' },
- { key: 'rollouts', icon: Zap, labelKey: 'app.agentFleet.rollouts', label: 'Rollouts' },
- { key: 'queue', icon: Clock, labelKey: 'app.agentFleet.commandQueue', label: 'Command queue' },
- { key: 'discovery', icon: Search, labelKey: 'app.agentFleet.discovery', label: 'Discovery' },
- { key: 'approvals', icon: Shield, labelKey: 'app.agentFleet.approvals', label: 'Approvals' },
- ].map(tab => (
-
-
- {tab.label}
- {tab.key === 'approvals' && pendingServers.length > 0 && (
- {pendingServers.length}
- )}
- {tab.key === 'queue' && queuedCommands.length > 0 && (
- {queuedCommands.length}
- )}
-
- ))}
-
-
-
{loadError && !tabHasData ? (
{
const [servers, setServers] = useState([]);
const [costs, setCosts] = useState(null);
const [loading, setLoading] = useState(true);
+ const [view, setView] = useState('servers');
const [showCreateProvider, setShowCreateProvider] = useState(false);
const [showCreateServer, setShowCreateServer] = useState(false);
const [providerOptions, setProviderOptions] = useState(null);
@@ -54,15 +56,26 @@ const CloudProvision = () => {
useEffect(() => { loadData(); }, [loadData]);
// Publish the admin actions to the shared tab-group top bar.
- useTopbarActions(() =>
- user?.is_admin ? (
- <>
-
-
- >
- ) : null,
- [user?.is_admin]
- );
+ useTopbarActions(() => (
+ <>
+
+ {user?.is_admin && (
+ <>
+
+
+ >
+ )}
+ >
+ ), [user?.is_admin, view]);
const handleCreateProvider = async () => {
try {
@@ -112,13 +125,7 @@ const CloudProvision = () => {
return (
-
-
- {t('common.labels.servers', 'Servers')}
- {t('app.cloudProvision.providers', 'Providers')}
- {t('app.cloudProvision.costs', 'Costs')}
-
-
+
{servers.map(srv => (
diff --git a/frontend/src/pages/FTPServer.jsx b/frontend/src/pages/FTPServer.jsx
index 903a53c2..2d90c271 100644
--- a/frontend/src/pages/FTPServer.jsx
+++ b/frontend/src/pages/FTPServer.jsx
@@ -6,7 +6,7 @@ import {
import useTabParam from '../hooks/useTabParam';
import { useTopbarActions } from '@/hooks/useTopbarActions';
import { api } from '../services/api';
-import { DataTable, DataTableFooter, MetricCard, KpiBand, Pill, ListToolbar } from '@/components/ds';
+import { DataTable, DataTableFooter, MetricCard, KpiBand, Pill, ListToolbar, SegControl } from '@/components/ds';
import {
useTableChrome, GridViewPicker, GridChips, GridFilterButton,
GridToolsMenu, GridFilterDrawer,
@@ -23,7 +23,7 @@ import { Label } from '@/components/ui/label';
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '@/components/ui/select';
import { InfoList, InfoItem } from '../components/InfoList';
import { Badge } from '@/components/ui/badge';
-import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
+import { Tabs, TabsContent } from '@/components/ui/tabs';
import { useTranslation } from 'react-i18next';
import { errorReason, toastError } from '@/utils/errorMessage';
@@ -498,8 +498,27 @@ function FTPServer() {
const isInstalled = status?.any_installed;
const isRunning = status?.any_running;
+ const selectTab = (val) => {
+ setActiveTab(val);
+ if (val === 'connections') loadConnections();
+ if (val === 'logs') loadLogs();
+ };
+
useTopbarActions(() =>
<>
+ {isInstalled && (
+
+ )}
{!isInstalled ? (