Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 14 additions & 17 deletions src/components/AppsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
import { useState, useEffect } from 'react';
import { useShallow } from 'zustand/shallow';
import { Cpu, Database, Zap, Settings, AlertTriangle, ChevronRight, Box, Network, FileCode } from 'lucide-react';
import { AlertTriangle, Box, ChevronRight, Cpu, Database, FileCode, Network, Settings, Zap } from 'lucide-react';
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { useAppStore } from '@/lib/store';
import { ConfigurationPanel } from '@/components/ConfigurationPanel';
import { FaultsPanel } from '@/components/FaultsPanel';
import { OperationsPanel } from '@/components/OperationsPanel';
import {
RESOURCE_TABS,
renderResourceTabContent,
isResourceTabId,
type ResourceTabId,
} from '@/components/ResourceTabs';
import type { ComponentTopic, Operation, Fault } from '@/lib/types';

type AppTab = 'overview' | 'data' | 'operations' | 'configurations' | 'faults';
type AppTab = 'overview' | ResourceTabId;

interface TabConfig {
id: AppTab;
label: string;
icon: typeof Database;
}

const APP_TABS: TabConfig[] = [
{ id: 'overview', label: 'Overview', icon: Cpu },
{ id: 'data', label: 'Data', icon: Database },
{ id: 'operations', label: 'Operations', icon: Zap },
{ id: 'configurations', label: 'Config', icon: Settings },
{ id: 'faults', label: 'Faults', icon: AlertTriangle },
];
const APP_TABS: TabConfig[] = [{ id: 'overview', label: 'Overview', icon: Cpu }, ...RESOURCE_TABS];

interface AppsPanelProps {
appId: string;
Expand Down Expand Up @@ -313,11 +310,11 @@ export function AppsPanel({ appId, appName, fqn, nodeName, namespace, componentI
</Card>
)}

{activeTab === 'operations' && <OperationsPanel entityId={appId} entityType="apps" />}

{activeTab === 'configurations' && <ConfigurationPanel entityId={appId} entityType="apps" />}

{activeTab === 'faults' && <FaultsPanel entityId={appId} entityType="apps" />}
{/* Operations / Configurations / Faults / Logs delegated to the shared helper */}
{activeTab !== 'overview' &&
activeTab !== 'data' &&
isResourceTabId(activeTab) &&
renderResourceTabContent(activeTab, appId, 'apps')}

{isLoading && <div className="text-center text-muted-foreground py-4">Loading app resources...</div>}
</div>
Expand Down
86 changes: 46 additions & 40 deletions src/components/EntityDetailPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import {
ArrowUp,
ArrowDown,
Database,
Zap,
Settings,
RefreshCw,
Box,
Layers,
Cpu,
GitBranch,
Home,
AlertTriangle,
Server,
} from 'lucide-react';
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
Expand All @@ -26,7 +24,7 @@ import { EntityDetailSkeleton } from '@/components/EntityDetailSkeleton';
import { DataPanel } from '@/components/DataPanel';
import { ConfigurationPanel } from '@/components/ConfigurationPanel';
import { OperationsPanel } from '@/components/OperationsPanel';
import { FaultsPanel } from '@/components/FaultsPanel';
import { RESOURCE_TABS, renderResourceTabContent, type ResourceTabId } from '@/components/ResourceTabs';
import { AreasPanel } from '@/components/AreasPanel';
import { AppsPanel } from '@/components/AppsPanel';
import { FunctionsPanel } from '@/components/FunctionsPanel';
Expand All @@ -35,21 +33,16 @@ import { FaultsDashboard } from '@/components/FaultsDashboard';
import { useAppStore, type AppState } from '@/lib/store';
import type { ComponentTopic, Parameter, SovdResourceEntityType } from '@/lib/types';

type ComponentTab = 'data' | 'operations' | 'configurations' | 'faults';
type ComponentTab = ResourceTabId;

interface TabConfig {
id: ComponentTab;
label: string;
icon: typeof Database;
description: string;
description?: string;
}

const COMPONENT_TABS: TabConfig[] = [
{ id: 'data', label: 'Data', icon: Database, description: 'Data items & messages' },
{ id: 'operations', label: 'Operations', icon: Zap, description: 'Services & actions' },
{ id: 'configurations', label: 'Config', icon: Settings, description: 'Parameters' },
{ id: 'faults', label: 'Faults', icon: AlertTriangle, description: 'Diagnostic trouble codes' },
];
const COMPONENT_TABS: TabConfig[] = RESOURCE_TABS;

/**
* Determine entity type for API calls based on entity type
Expand Down Expand Up @@ -113,26 +106,18 @@ function ComponentTabContent({
entityType,
topicsData,
}: ComponentTabContentProps) {
switch (activeTab) {
case 'data':
return (
<DataTabContent
selectedPath={selectedPath}
selectedEntity={selectedEntity}
hasTopicsInfo={hasTopicsInfo}
selectEntity={selectEntity}
topicsData={topicsData}
/>
);
case 'operations':
return <OperationsPanel entityId={entityId} entityType={entityType} />;
case 'configurations':
return <ConfigurationPanel entityId={entityId} entityType={entityType} />;
case 'faults':
return <FaultsPanel entityId={entityId} entityType={entityType} />;
default:
return null;
if (activeTab === 'data') {
return (
<DataTabContent
selectedPath={selectedPath}
selectedEntity={selectedEntity}
hasTopicsInfo={hasTopicsInfo}
selectEntity={selectEntity}
topicsData={topicsData}
/>
);
}
return <>{renderResourceTabContent(activeTab, entityId, entityType)}</>;
}

/**
Expand Down Expand Up @@ -351,12 +336,13 @@ interface EntityDetailPanelProps {

export function EntityDetailPanel({ onConnectClick, viewMode = 'entity', onEntitySelect }: EntityDetailPanelProps) {
const [activeTab, setActiveTab] = useState<ComponentTab>('data');
const [resourceCounts, setResourceCounts] = useState<{
data: number;
operations: number;
configurations: number;
faults: number;
}>({ data: 0, operations: 0, configurations: 0, faults: 0 });
const [resourceCounts, setResourceCounts] = useState<Record<ResourceTabId, number>>({
data: 0,
operations: 0,
configurations: 0,
faults: 0,
logs: 0,
});
// Store fetched topics data for the Data tab
const [topicsData, setTopicsData] = useState<ComponentTopic[]>([]);

Expand Down Expand Up @@ -391,11 +377,24 @@ export function EntityDetailPanel({ onConnectClick, viewMode = 'entity', onEntit
}
}, [selectedPath, onEntitySelect]);

// Reset the component-view resource tab to Data when the entity changes,
// so switching between components doesn't show stale tab state.
useEffect(() => {
setActiveTab('data');
}, [selectedEntity?.id]);

// Fetch resource counts when entity changes
useEffect(() => {
const emptyCounts: Record<ResourceTabId, number> = {
data: 0,
operations: 0,
configurations: 0,
faults: 0,
logs: 0,
};
const doFetchResourceCounts = async () => {
if (!selectedEntity) {
setResourceCounts({ data: 0, operations: 0, configurations: 0, faults: 0 });
setResourceCounts(emptyCounts);
setTopicsData([]);
return;
}
Expand All @@ -408,7 +407,7 @@ export function EntityDetailPanel({ onConnectClick, viewMode = 'entity', onEntit

// Only fetch counts for entity types that have resources
if (!isComponent && !isApp && !isArea && !isFunction) {
setResourceCounts({ data: 0, operations: 0, configurations: 0, faults: 0 });
setResourceCounts(emptyCounts);
setTopicsData([]);
return;
}
Expand All @@ -431,7 +430,7 @@ export function EntityDetailPanel({ onConnectClick, viewMode = 'entity', onEntit
setTopicsData(fetchedData);

// Use the already-fetched data length instead of a separate request
setResourceCounts({ ...counts, data: fetchedData.length });
setResourceCounts({ ...counts, data: fetchedData.length, logs: 0 });
} catch {
// Silently handle errors - counts will stay at 0
}
Expand Down Expand Up @@ -652,12 +651,18 @@ export function EntityDetailPanel({ onConnectClick, viewMode = 'entity', onEntit

{/* Area Entity View */}
{isArea && !hasError && (
<AreasPanel areaId={selectedEntity.id} areaName={selectedEntity.name} path={selectedPath} />
<AreasPanel
key={selectedEntity.id}
areaId={selectedEntity.id}
areaName={selectedEntity.name}
path={selectedPath}
/>
)}

{/* App Entity View */}
{isApp && !hasError && (
<AppsPanel
key={selectedEntity.id}
appId={selectedEntity.id}
appName={selectedEntity.name}
fqn={selectedEntity.fqn as string | undefined}
Expand All @@ -672,6 +677,7 @@ export function EntityDetailPanel({ onConnectClick, viewMode = 'entity', onEntit
{/* Function Entity View */}
{isFunction && !hasError && (
<FunctionsPanel
key={selectedEntity.id}
functionId={selectedEntity.id}
functionName={selectedEntity.name}
description={selectedEntity.description as string | undefined}
Expand Down
53 changes: 22 additions & 31 deletions src/components/EntityResourceTabs.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,13 @@
import { useState, useEffect, useCallback, useRef } from 'react';
import { useShallow } from 'zustand/shallow';
import { Database, Zap, Settings, AlertTriangle, Loader2, MessageSquare } from 'lucide-react';
import { Database, Loader2, MessageSquare } from 'lucide-react';
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { useAppStore } from '@/lib/store';
import { ConfigurationPanel } from '@/components/ConfigurationPanel';
import { OperationsPanel } from '@/components/OperationsPanel';
import { FaultsPanel } from '@/components/FaultsPanel';
import { RESOURCE_TABS, renderResourceTabContent, type ResourceTabId } from '@/components/ResourceTabs';
import type { SovdResourceEntityType } from '@/lib/types';
import type { ComponentTopic, Operation, Fault } from '@/lib/types';

type ResourceTab = 'data' | 'operations' | 'configurations' | 'faults';

interface TabConfig {
id: ResourceTab;
label: string;
icon: typeof Database;
}

const RESOURCE_TABS: TabConfig[] = [
{ id: 'data', label: 'Data', icon: Database },
{ id: 'operations', label: 'Operations', icon: Zap },
{ id: 'configurations', label: 'Config', icon: Settings },
{ id: 'faults', label: 'Faults', icon: AlertTriangle },
];

interface EntityResourceTabsProps {
entityId: string;
entityType: SovdResourceEntityType;
Expand All @@ -39,6 +22,7 @@ interface LoadedResources {
operations: boolean;
configurations: boolean;
faults: boolean;
logs: boolean;
}

/**
Expand All @@ -48,13 +32,14 @@ interface LoadedResources {
* Resources are lazy-loaded per tab to avoid unnecessary API calls.
*/
export function EntityResourceTabs({ entityId, entityType, basePath, onNavigate }: EntityResourceTabsProps) {
const [activeTab, setActiveTab] = useState<ResourceTab>('data');
const [activeTab, setActiveTab] = useState<ResourceTabId>('data');
const [isLoading, setIsLoading] = useState(false);
const [loadedTabs, setLoadedTabs] = useState<LoadedResources>({
data: false,
operations: false,
configurations: false,
faults: false,
logs: false,
});
const loadedTabsRef = useRef(loadedTabs);
loadedTabsRef.current = loadedTabs;
Expand Down Expand Up @@ -82,7 +67,7 @@ export function EntityResourceTabs({ entityId, entityType, basePath, onNavigate

// Lazy load resources for the active tab
const loadTabResources = useCallback(
async (tab: ResourceTab) => {
async (tab: ResourceTabId) => {
if (loadedTabsRef.current[tab]) return;

setIsLoading(true);
Expand Down Expand Up @@ -111,6 +96,10 @@ export function EntityResourceTabs({ entityId, entityType, basePath, onNavigate
setFaults(faultsRes.items || []);
break;
}
case 'logs': {
// LogsPanel owns its own fetching; no parent-level count fetch.
break;
}
}
setLoadedTabs((prev) => ({ ...prev, [tab]: true }));
} catch (error) {
Expand All @@ -123,6 +112,16 @@ export function EntityResourceTabs({ entityId, entityType, basePath, onNavigate
[fetchEntityData, fetchEntityOperations, fetchConfigurations, listEntityFaults, entityId, entityType]
);

// Reset tab state when the entity changes so stale data from the
// previous entity does not leak into the new one.
useEffect(() => {
setActiveTab('data');
setLoadedTabs({ data: false, operations: false, configurations: false, faults: false, logs: false });
setData([]);
setOperations([]);
setFaults([]);
}, [entityId, entityType]);

// Load resources when tab changes
useEffect(() => {
loadTabResources(activeTab);
Expand Down Expand Up @@ -227,16 +226,8 @@ export function EntityResourceTabs({ entityId, entityType, basePath, onNavigate
</Card>
)}

{/* Operations Tab */}
{activeTab === 'operations' && <OperationsPanel entityId={entityId} entityType={entityType} />}

{/* Configurations Tab */}
{activeTab === 'configurations' && (
<ConfigurationPanel entityId={entityId} entityType={entityType} />
)}

{/* Faults Tab */}
{activeTab === 'faults' && <FaultsPanel entityId={entityId} entityType={entityType} />}
{/* Operations / Configurations / Faults / Logs delegated to shared helper */}
{activeTab !== 'data' && renderResourceTabContent(activeTab, entityId, entityType)}
</>
)}
</div>
Expand Down
Loading
Loading