Skip to content

Commit 877ff8c

Browse files
authored
Merge pull request #66 from oslabs-beta/dev
Dev
2 parents 366ca80 + fa31df7 commit 877ff8c

File tree

7 files changed

+493
-374
lines changed

7 files changed

+493
-374
lines changed

client/src/App.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import sunIcon from './assets/sun.svg';
1212
import Header from './components/Header';
1313
import { SignedIn, SignedOut } from '@clerk/clerk-react';
1414
import { Routes, Route } from 'react-router-dom';
15+
import { ClusterProvider } from './contexts/ClusterContext';
1516

1617
const App = () => {
1718
const [isDark, setIsDark] = useState(() => {
@@ -80,17 +81,19 @@ const App = () => {
8081
</div>
8182
</SignedOut>
8283
<SignedIn>
83-
{/* Create a fixed viewport container that accounts for header and nav */}
84-
<div className="fixed bottom-0 left-0 right-0 top-16 flex flex-col">
85-
{/* Scrollable content area that stops at navigation bar */}
86-
<div className="flex-1 overflow-y-auto pb-24">
87-
<Routes>
88-
<Route path="/" element={<Dashboard />} />
89-
<Route path="/historical" element={<HistoricalData />} />
90-
</Routes>
84+
<ClusterProvider>
85+
{/* Create a fixed viewport container that accounts for header and nav */}
86+
<div className="fixed bottom-0 left-0 right-0 top-16 flex flex-col">
87+
{/* Scrollable content area that stops at navigation bar */}
88+
<div className="flex-1 overflow-y-auto pb-24">
89+
<Routes>
90+
<Route path="/" element={<Dashboard />} />
91+
<Route path="/historical" element={<HistoricalData />} />
92+
</Routes>
93+
</div>
9194
</div>
92-
</div>
93-
<NavigationBar />
95+
<NavigationBar />
96+
</ClusterProvider>
9497
</SignedIn>
9598
</div>
9699
);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React from 'react';
2+
import {
3+
Select,
4+
SelectContent,
5+
SelectItem,
6+
SelectTrigger,
7+
SelectValue,
8+
} from '@/components/ui/select';
9+
import { useCluster } from '@/contexts/ClusterContext';
10+
import { ServerIcon, MapPinIcon } from 'lucide-react';
11+
12+
const ClusterSelector: React.FC = () => {
13+
const { clusters, selectedCluster, setSelectedCluster, loading } = useCluster();
14+
15+
if (loading) {
16+
return (
17+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
18+
<ServerIcon className="h-4 w-4" />
19+
Loading clusters...
20+
</div>
21+
);
22+
}
23+
24+
if (clusters.length === 0) {
25+
return (
26+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
27+
<ServerIcon className="h-4 w-4" />
28+
No clusters available
29+
</div>
30+
);
31+
}
32+
33+
const handleClusterChange = (value: string) => {
34+
const [name, location] = value.split('|');
35+
const cluster = clusters.find(c => c.name === name && c.location === location);
36+
if (cluster) {
37+
setSelectedCluster(cluster);
38+
}
39+
};
40+
41+
const selectedValue = selectedCluster
42+
? `${selectedCluster.name}|${selectedCluster.location}`
43+
: '';
44+
45+
return (
46+
<div className="flex items-center gap-3">
47+
<div className="flex items-center gap-2 text-sm font-medium">
48+
<ServerIcon className="h-4 w-4 text-primary" />
49+
<span>Cluster:</span>
50+
</div>
51+
<Select value={selectedValue} onValueChange={handleClusterChange}>
52+
<SelectTrigger className="w-[280px]">
53+
<SelectValue placeholder="Select a cluster" />
54+
</SelectTrigger>
55+
<SelectContent>
56+
{clusters.map((cluster) => {
57+
const value = `${cluster.name}|${cluster.location}`;
58+
return (
59+
<SelectItem key={value} value={value}>
60+
<div className="flex items-center gap-2">
61+
<div className="flex flex-col">
62+
<span className="font-medium">{cluster.name}</span>
63+
<div className="flex items-center gap-1 text-xs text-muted-foreground">
64+
<MapPinIcon className="h-3 w-3" />
65+
{cluster.location}
66+
</div>
67+
</div>
68+
</div>
69+
</SelectItem>
70+
);
71+
})}
72+
</SelectContent>
73+
</Select>
74+
</div>
75+
);
76+
};
77+
78+
export default ClusterSelector;
79+

client/src/components/EnhancedInfoCard.tsx

Lines changed: 30 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,11 @@ import {
1212
} from 'lucide-react';
1313
import { useFetchMetrics } from '../hooks/hookMetric';
1414
import { motion } from 'motion/react';
15-
import axios from 'axios';
15+
import { useCluster } from '@/contexts/ClusterContext';
16+
import ClusterSelector from './ClusterSelector';
1617

1718
const duration = 5;
1819

19-
interface ClusterInfo {
20-
name: string;
21-
location: string;
22-
status: string;
23-
nodeCount: number;
24-
network: string;
25-
}
26-
27-
const useClusterInfo = () => {
28-
const [data, setData] = useState<ClusterInfo | null>(null);
29-
const [loading, setLoading] = useState(false);
30-
const [error, setError] = useState<string | null>(null);
31-
32-
useEffect(() => {
33-
const fetchCluster = async () => {
34-
setLoading(true);
35-
try {
36-
const baseUrl = import.meta.env.VITE_API_URL || '';
37-
const res = await axios.get<ClusterInfo>(`${baseUrl}/api/gke/cluster`);
38-
setData(res.data);
39-
} catch (err) {
40-
setError(`Failed to fetch cluster info, ${err}`);
41-
} finally {
42-
setLoading(false);
43-
}
44-
};
45-
46-
fetchCluster();
47-
}, []);
48-
49-
return { data, loading, error };
50-
};
51-
5220
const StatusBadge = ({ status }: { status: string }) => {
5321
const isRunning = status.toLowerCase() === 'running';
5422

@@ -75,7 +43,7 @@ const StatusBadge = ({ status }: { status: string }) => {
7543
};
7644

7745
const GKEClusterCard: React.FC = () => {
78-
const { data: cluster, loading: clusterLoading } = useClusterInfo();
46+
const { selectedCluster: cluster, loading: clusterLoading, error } = useCluster();
7947

8048
const cpuMetric = 'kubernetes.io/container/cpu/limit_utilization';
8149
const memMetric = 'kubernetes.io/container/memory/request_utilization';
@@ -101,7 +69,8 @@ const GKEClusterCard: React.FC = () => {
10169
}, [cpuData, memData]);
10270

10371
if (clusterLoading) return <p>Loading cluster info...</p>;
104-
if (!cluster) return <p>Cluster data not available</p>;
72+
if (error) return <p>Error: {error}</p>;
73+
if (!cluster) return <p>No cluster selected</p>;
10574

10675
const { name, location, status, nodeCount, network } = cluster;
10776

@@ -113,22 +82,32 @@ const GKEClusterCard: React.FC = () => {
11382
>
11483
<Card className="overflow-hidden rounded-2xl border shadow-md transition-shadow duration-300 hover:shadow-lg">
11584
<CardHeader>
116-
<div className="flex items-start justify-between">
117-
<div>
118-
<CardTitle className="from-primary to-secondary bg-gradient-to-r bg-clip-text text-base font-bold text-transparent md:text-2xl">
119-
<span className="hidden md:inline">
120-
Google Kubernetes Engine Cluster Health
121-
</span>
122-
<span className="inline md:hidden">GKE Cluster Health</span>
123-
</CardTitle>
124-
{/* <p className="text-muted-foreground mt-1">
125-
Real-time monitoring and metrics for your GKE cluster
126-
</p> */}
127-
<p className="text-muted-foreground text-sm">
128-
Region: {location}
129-
</p>
85+
<div className="space-y-4">
86+
<div className="flex items-start justify-between">
87+
<div>
88+
<CardTitle className="from-primary to-secondary bg-gradient-to-r bg-clip-text text-base font-bold text-transparent md:text-2xl">
89+
<span className="hidden md:inline">
90+
Google Kubernetes Engine Cluster Health
91+
</span>
92+
<span className="inline md:hidden">GKE Cluster Health</span>
93+
</CardTitle>
94+
{/* <p className="text-muted-foreground mt-1">
95+
Real-time monitoring and metrics for your GKE cluster
96+
</p> */}
97+
<p className="text-muted-foreground text-sm">
98+
Region: {location}
99+
</p>
100+
</div>
101+
<StatusBadge status={status} />
130102
</div>
131-
<StatusBadge status={status} />
103+
{/* Cluster Selector */}
104+
<motion.div
105+
initial={{ opacity: 0, y: 10 }}
106+
animate={{ opacity: 1, y: 0 }}
107+
transition={{ delay: 0.2 }}
108+
>
109+
<ClusterSelector />
110+
</motion.div>
132111
</div>
133112
</CardHeader>
134113

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
2+
import axios from 'axios';
3+
4+
interface ClusterInfo {
5+
name: string;
6+
location: string;
7+
status: string;
8+
nodeCount: number;
9+
network: string;
10+
}
11+
12+
interface ClusterContextType {
13+
clusters: ClusterInfo[];
14+
selectedCluster: ClusterInfo | null;
15+
setSelectedCluster: (cluster: ClusterInfo) => void;
16+
loading: boolean;
17+
error: string | null;
18+
}
19+
20+
const ClusterContext = createContext<ClusterContextType | undefined>(undefined);
21+
22+
export const useCluster = () => {
23+
const context = useContext(ClusterContext);
24+
if (!context) {
25+
throw new Error('useCluster must be used within a ClusterProvider');
26+
}
27+
return context;
28+
};
29+
30+
interface ClusterProviderProps {
31+
children: ReactNode;
32+
}
33+
34+
export const ClusterProvider: React.FC<ClusterProviderProps> = ({ children }) => {
35+
const [clusters, setClusters] = useState<ClusterInfo[]>([]);
36+
const [selectedCluster, setSelectedCluster] = useState<ClusterInfo | null>(null);
37+
const [loading, setLoading] = useState(false);
38+
const [error, setError] = useState<string | null>(null);
39+
40+
useEffect(() => {
41+
const fetchClusters = async () => {
42+
setLoading(true);
43+
try {
44+
const baseUrl = import.meta.env.VITE_API_URL || '';
45+
const res = await axios.get<ClusterInfo[]>(`${baseUrl}/api/gke/clusters`);
46+
setClusters(res.data);
47+
// Auto-select the first cluster if available
48+
if (res.data.length > 0) {
49+
setSelectedCluster(res.data[0]);
50+
}
51+
} catch (err) {
52+
setError(`Failed to fetch clusters: ${err}`);
53+
} finally {
54+
setLoading(false);
55+
}
56+
};
57+
58+
fetchClusters();
59+
}, []);
60+
61+
const handleSetSelectedCluster = (cluster: ClusterInfo) => {
62+
setSelectedCluster(cluster);
63+
};
64+
65+
return (
66+
<ClusterContext.Provider
67+
value={{
68+
clusters,
69+
selectedCluster,
70+
setSelectedCluster: handleSetSelectedCluster,
71+
loading,
72+
error,
73+
}}
74+
>
75+
{children}
76+
</ClusterContext.Provider>
77+
);
78+
};
79+

0 commit comments

Comments
 (0)