feat(ui): add type filter toggles
This commit is contained in:
173
src/store/topologyStore.ts
Normal file
173
src/store/topologyStore.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||
import { ViewMode, TopologyNode, TopologyEdge, NetworkInfo, Host, NodeType } from '../types';
|
||||
|
||||
export type Orientation = 'LR' | 'TB';
|
||||
export type StatusFilter = 'all' | 'running' | 'stopped';
|
||||
|
||||
// All node types for default filter
|
||||
const ALL_NODE_TYPES: NodeType[] = [
|
||||
'gateway', 'vlan', 'wifi', 'host_physical', 'host_vm', 'host_container',
|
||||
'service', 'volume', 'mount', 'path'
|
||||
];
|
||||
|
||||
interface TopologyState {
|
||||
nodes: TopologyNode[];
|
||||
edges: TopologyEdge[];
|
||||
selectedNodeId: string | null;
|
||||
viewMode: ViewMode;
|
||||
orientation: Orientation;
|
||||
searchQuery: string;
|
||||
typeFilters: NodeType[];
|
||||
statusFilter: StatusFilter;
|
||||
leftPanelOpen: boolean;
|
||||
rightPanelOpen: boolean;
|
||||
lastUpdated: Date | null;
|
||||
isLoading: boolean;
|
||||
|
||||
networkInfo: NetworkInfo | null;
|
||||
hosts: Host[];
|
||||
|
||||
setNodes: (nodes: TopologyNode[]) => void;
|
||||
setEdges: (edges: TopologyEdge[]) => void;
|
||||
setSelectedNode: (nodeId: string | null) => void;
|
||||
setViewMode: (mode: ViewMode) => void;
|
||||
setOrientation: (orientation: Orientation) => void;
|
||||
setSearchQuery: (query: string) => void;
|
||||
toggleTypeFilter: (type: NodeType) => void;
|
||||
setStatusFilter: (filter: StatusFilter) => void;
|
||||
toggleLeftPanel: () => void;
|
||||
toggleRightPanel: () => void;
|
||||
setLastUpdated: (date: Date) => void;
|
||||
setIsLoading: (loading: boolean) => void;
|
||||
setNetworkInfo: (info: NetworkInfo) => void;
|
||||
setHosts: (hosts: Host[]) => void;
|
||||
|
||||
getSelectedNode: () => TopologyNode | null;
|
||||
getChildNodes: () => TopologyNode[];
|
||||
getFilteredNodes: () => TopologyNode[];
|
||||
}
|
||||
|
||||
export const useTopologyStore = create<TopologyState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
nodes: [],
|
||||
edges: [],
|
||||
selectedNodeId: null,
|
||||
viewMode: 'full',
|
||||
orientation: 'LR',
|
||||
searchQuery: '',
|
||||
typeFilters: ALL_NODE_TYPES,
|
||||
statusFilter: 'all',
|
||||
leftPanelOpen: true,
|
||||
rightPanelOpen: true,
|
||||
lastUpdated: null,
|
||||
isLoading: false,
|
||||
networkInfo: null,
|
||||
hosts: [],
|
||||
|
||||
setNodes: (nodes) => set({ nodes }),
|
||||
setEdges: (edges) => set({ edges }),
|
||||
setSelectedNode: (nodeId) => set({ selectedNodeId: nodeId }),
|
||||
setViewMode: (mode) => set({ viewMode: mode }),
|
||||
setOrientation: (orientation) => set({ orientation }),
|
||||
setSearchQuery: (query) => set({ searchQuery: query }),
|
||||
toggleTypeFilter: (type) => set((state) => {
|
||||
const exists = state.typeFilters.includes(type);
|
||||
return {
|
||||
typeFilters: exists
|
||||
? state.typeFilters.filter(t => t !== type)
|
||||
: [...state.typeFilters, type]
|
||||
};
|
||||
}),
|
||||
setStatusFilter: (filter) => set({ statusFilter: filter }),
|
||||
toggleLeftPanel: () => set((state) => ({ leftPanelOpen: !state.leftPanelOpen })),
|
||||
toggleRightPanel: () => set((state) => ({ rightPanelOpen: !state.rightPanelOpen })),
|
||||
setLastUpdated: (date) => set({ lastUpdated: date }),
|
||||
setIsLoading: (loading) => set({ isLoading: loading }),
|
||||
setNetworkInfo: (info) => set({ networkInfo: info }),
|
||||
setHosts: (hosts) => set({ hosts }),
|
||||
|
||||
getSelectedNode: () => {
|
||||
const { nodes, selectedNodeId } = get();
|
||||
return nodes.find(n => n.id === selectedNodeId) || null;
|
||||
},
|
||||
|
||||
getChildNodes: () => {
|
||||
const { nodes, selectedNodeId } = get();
|
||||
if (!selectedNodeId) return [];
|
||||
const selectedNode = nodes.find(n => n.id === selectedNodeId);
|
||||
if (!selectedNode) return [];
|
||||
return nodes.filter(n => n.data.parentId === selectedNodeId);
|
||||
},
|
||||
|
||||
getFilteredNodes: () => {
|
||||
const { nodes, viewMode, searchQuery, typeFilters, statusFilter } = get();
|
||||
|
||||
let filtered = nodes;
|
||||
|
||||
if (searchQuery) {
|
||||
const query = searchQuery.toLowerCase();
|
||||
filtered = filtered.filter(n =>
|
||||
n.name.toLowerCase().includes(query) ||
|
||||
n.data.ip?.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
if (statusFilter !== 'all') {
|
||||
filtered = filtered.filter(n => n.data.status === statusFilter);
|
||||
}
|
||||
|
||||
if (typeFilters.length > 0 && typeFilters.length < ALL_NODE_TYPES.length) {
|
||||
filtered = filtered.filter(n => typeFilters.includes(n.type));
|
||||
}
|
||||
|
||||
let allowedTypes: NodeType[] = [];
|
||||
if (viewMode === 'network') {
|
||||
allowedTypes = ['gateway', 'vlan', 'wifi', 'host_physical', 'host_vm', 'host_container'];
|
||||
} else if (viewMode === 'host') {
|
||||
allowedTypes = ['gateway', 'vlan', 'wifi', 'host_physical', 'host_vm', 'host_container'];
|
||||
} else if (viewMode === 'service') {
|
||||
allowedTypes = ['host_physical', 'host_vm', 'host_container', 'service', 'volume'];
|
||||
} else if (viewMode === 'filesystem') {
|
||||
allowedTypes = ['volume', 'mount', 'path'];
|
||||
}
|
||||
|
||||
if (allowedTypes.length > 0) {
|
||||
const nodeMap = new Map(nodes.map(n => [n.id, n]));
|
||||
const includeSet = new Set<string>();
|
||||
|
||||
nodes.forEach(node => {
|
||||
if (allowedTypes.includes(node.type)) {
|
||||
includeSet.add(node.id);
|
||||
|
||||
let current: TopologyNode | undefined = node;
|
||||
while (current?.data?.parentId) {
|
||||
const parentId = current.data.parentId;
|
||||
includeSet.add(parentId);
|
||||
current = nodeMap.get(parentId);
|
||||
if (!current) break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
filtered = filtered.filter(n => includeSet.has(n.id));
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: 'homelab-topology-settings',
|
||||
version: 1,
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
partialize: (state: TopologyState) => ({
|
||||
viewMode: state.viewMode,
|
||||
orientation: state.orientation,
|
||||
searchQuery: state.searchQuery,
|
||||
typeFilters: state.typeFilters,
|
||||
statusFilter: state.statusFilter,
|
||||
leftPanelOpen: state.leftPanelOpen,
|
||||
rightPanelOpen: state.rightPanelOpen
|
||||
})
|
||||
}));
|
||||
Reference in New Issue
Block a user