Skip to content

Commit 970aca6

Browse files
authored
Merge pull request #5 from chaehee-lim/feature/my-fix
fixed UI for dashboard
2 parents 0f1f95d + 17ace1f commit 970aca6

File tree

4 files changed

+203
-199
lines changed

4 files changed

+203
-199
lines changed

src/components/Cluster.tsx

Lines changed: 136 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-FileCopyrightText: Copyright 2024 LG Electronics Inc.
22
// SPDX-License-Identifier: Apache-2.0
3-
import { useState } from "react";
3+
import { useEffect, useState } from "react";
44
import {
55
Card,
66
CardContent,
@@ -19,6 +19,7 @@ import {
1919
import { Badge } from "./ui/badge";
2020
import { Button } from "./ui/button";
2121
import { Input } from "./ui/input";
22+
import { Progress } from "./ui/progress";
2223
import {
2324
Search,
2425
MoreHorizontal,
@@ -33,44 +34,116 @@ export function Cluster() {
3334
const [searchTerm, setSearchTerm] = useState("");
3435

3536
// Mock data
36-
const nodes = [
37-
{
38-
name: "master-node-1",
39-
internalIP: "192.168.1.10",
40-
os: "Ubuntu 22.04",
41-
arch: "amd64",
42-
cpuCapacity: "4",
43-
memoryCapacity: "8Gi",
44-
storage: "50Gi",
45-
},
46-
{
47-
name: "worker-node-1",
48-
internalIP: "192.168.1.11",
49-
os: "Ubuntu 22.04",
50-
arch: "amd64",
51-
cpuCapacity: "8",
52-
memoryCapacity: "16Gi",
53-
storage: "100Gi",
54-
},
55-
{
56-
name: "worker-node-2",
57-
internalIP: "192.168.1.12",
58-
os: "Ubuntu 22.04",
59-
arch: "amd64",
60-
cpuCapacity: "8",
61-
memoryCapacity: "16Gi",
62-
storage: "100Gi",
63-
},
64-
{
65-
name: "worker-node-3",
66-
internalIP: "192.168.1.13",
67-
os: "Ubuntu 22.04",
68-
arch: "arm64",
69-
cpuCapacity: "8",
70-
memoryCapacity: "16Gi",
71-
storage: "100Gi",
72-
},
73-
];
37+
// nodes fetched from settingsservice
38+
const [nodesDataToUse, setNodesDataToUse] = useState<any[]>([]);
39+
const [nodesFetchSuccess, setNodesFetchSuccess] = useState(false);
40+
const [nodesFetchError, setNodesFetchError] = useState<string | null>(null);
41+
42+
useEffect(() => {
43+
const settingserviceApiUrl = import.meta.env.VITE_SETTING_SERVICE_API_URL;
44+
const endpoint = settingserviceApiUrl
45+
? `${settingserviceApiUrl.replace(/\/+$/, "")}/api/v1/nodes`
46+
: "/api/v1/nodes";
47+
48+
const fetchNodes = async () => {
49+
setNodesFetchError(null);
50+
const tryRelative = async () => {
51+
// Try the metrics path first (Workloads uses /api/v1/metrics/nodes)
52+
const candidates = [
53+
"/api/v1/metrics/nodes",
54+
"/api/v1/nodes",
55+
];
56+
for (const path of candidates) {
57+
try {
58+
const res = await fetch(path);
59+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
60+
return await res.json();
61+
} catch (e) {
62+
console.debug(`Relative ${path} fetch failed:`, e);
63+
// try next
64+
}
65+
}
66+
throw new Error("All relative node endpoints failed");
67+
};
68+
69+
const tryAbsolute = async () => {
70+
if (!endpoint) throw new Error("No endpoint configured");
71+
try {
72+
const res = await fetch(endpoint);
73+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
74+
return await res.json();
75+
} catch (e) {
76+
console.debug("Absolute settingsservice fetch failed:", e);
77+
throw e;
78+
}
79+
};
80+
81+
let data: any = null;
82+
try {
83+
// Prefer relative path (dev proxy or same-origin)
84+
data = await tryRelative();
85+
} catch (eRel) {
86+
// If relative failed, try absolute (explicit settingservice URL)
87+
try {
88+
data = await tryAbsolute();
89+
} catch (eAbs) {
90+
const msg = (eAbs && (eAbs as Error).message) || String(eAbs || eRel);
91+
console.error("Nodes fetch failed:", eAbs || eRel);
92+
setNodesFetchError(msg);
93+
setNodesFetchSuccess(false);
94+
setNodesDataToUse([]);
95+
return;
96+
}
97+
}
98+
99+
// Normalize response shape
100+
let nodes: any[] = [];
101+
if (Array.isArray(data)) nodes = data;
102+
else if (data && Array.isArray((data as any).nodes)) nodes = (data as any).nodes;
103+
else {
104+
setNodesFetchError("Unexpected response shape from nodes API");
105+
setNodesFetchSuccess(false);
106+
setNodesDataToUse([]);
107+
return;
108+
}
109+
110+
if (nodes.length > 0) {
111+
const normalized = nodes.map((n) => ({
112+
...n,
113+
cpu_usage: typeof n.cpu_usage === "number" ? n.cpu_usage : Number(n.cpu_usage) || 0,
114+
cpu_count: typeof n.cpu_count === "number" ? n.cpu_count : Number(n.cpu_count) || 0,
115+
used_memory: typeof n.used_memory === "number" ? n.used_memory : Number(n.used_memory) || 0,
116+
total_memory: typeof n.total_memory === "number" ? n.total_memory : Number(n.total_memory) || 0,
117+
mem_usage: typeof n.mem_usage === "number" ? n.mem_usage : Number(n.mem_usage) || 0,
118+
}));
119+
setNodesDataToUse(normalized);
120+
setNodesFetchSuccess(true);
121+
} else {
122+
setNodesFetchSuccess(false);
123+
setNodesDataToUse([]);
124+
}
125+
};
126+
127+
fetchNodes();
128+
const interval = setInterval(fetchNodes, import.meta.env.VITE_SETTING_SERVICE_TIMEOUT || 5000);
129+
return () => clearInterval(interval);
130+
}, []);
131+
132+
const GB = 1024 * 1024 * 1024;
133+
const nodes = nodesFetchSuccess
134+
? nodesDataToUse.map((node: any) => ({
135+
name: node.node_name,
136+
internalIP: node.ip || node.internal_ip || "",
137+
os: node.os || "",
138+
arch: node.arch || "",
139+
// Use raw values from API: cpu_usage and mem_usage
140+
cpuUsage: node.cpu_usage ?? 0,
141+
memoryUsage: node.mem_usage ?? 0,
142+
totalStorage: node.total_storage || node.storage_total || 0,
143+
storageUsage: node.storage_usage || node.used_storage || 0,
144+
// pods removed from this table per request
145+
}))
146+
: [];
74147

75148

76149

@@ -125,7 +198,7 @@ export function Cluster() {
125198
</div>
126199
<div>
127200
<CardTitle className="text-foreground">
128-
Cluster Nodes
201+
Nodes
129202
</CardTitle>
130203
<CardDescription>
131204
Physical and virtual machines in your cluster
@@ -134,7 +207,12 @@ export function Cluster() {
134207
</div>
135208
</CardHeader>
136209
<CardContent>
137-
<div className="overflow-hidden rounded-xl border border-border/30">
210+
<div className="overflow-hidden rounded-xl border border-border/30">
211+
{nodesFetchError && (
212+
<div className="p-3 text-sm text-yellow-700 bg-yellow-50 border-b border-yellow-100">
213+
Nodes fetch error: {nodesFetchError}
214+
</div>
215+
)}
138216
<Table>
139217
<TableHeader className="bg-muted/80">
140218
<TableRow className="border-border/30">
@@ -150,16 +228,9 @@ export function Cluster() {
150228
<TableHead className="font-semibold text-foreground">
151229
Internal IP
152230
</TableHead>
153-
<TableHead className="font-semibold text-foreground">
154-
CPU
155-
</TableHead>
156-
<TableHead className="font-semibold text-foreground">
157-
Memory
158-
</TableHead>
159-
<TableHead className="font-semibold text-foreground">
160-
Storage
161-
</TableHead>
162-
<TableHead className="font-semibold text-foreground"></TableHead>
231+
<TableHead className="font-semibold text-foreground">CPU Usage</TableHead>
232+
<TableHead className="font-semibold text-foreground">Memory Usage</TableHead>
233+
<TableHead className="font-semibold text-foreground">Storage</TableHead>
163234
</TableRow>
164235
</TableHeader>
165236
<TableBody>
@@ -183,37 +254,25 @@ export function Cluster() {
183254
{node.internalIP}
184255
</TableCell>
185256
<TableCell>
186-
<div className="flex items-center gap-1">
187-
<Cpu className="w-3 h-3 text-muted-foreground" />
188-
<span className="font-mono text-sm">
189-
{node.cpuCapacity}
190-
</span>
257+
<div className="flex items-center gap-2">
258+
<span className="font-mono text-sm">{typeof node.cpuUsage === 'number' ? node.cpuUsage.toFixed(2) : String(node.cpuUsage)}</span>
259+
<div className="w-24">
260+
<Progress value={Math.min(100, Math.max(0, Number(node.cpuUsage) || 0))} className="h-2" />
261+
</div>
191262
</div>
192263
</TableCell>
193264
<TableCell>
194-
<div className="flex items-center gap-1">
195-
<MemoryStick className="w-3 h-3 text-muted-foreground" />
196-
<span className="font-mono text-sm">
197-
{node.memoryCapacity}
198-
</span>
265+
<div className="flex items-center gap-2">
266+
<span className="font-mono text-sm">{typeof node.memoryUsage === 'number' ? node.memoryUsage.toFixed(2) : String(node.memoryUsage)}</span>
267+
<div className="w-36">
268+
<Progress value={Math.min(100, Math.max(0, Number(node.memoryUsage) || 0))} className="h-2" />
269+
</div>
199270
</div>
200271
</TableCell>
201-
<TableCell>
202-
<div className="flex items-center gap-1">
203-
<HardDrive className="w-3 h-3 text-muted-foreground" />
204-
<span className="font-mono text-sm">
205-
{node.storage}
206-
</span>
207-
</div>
208-
</TableCell>
209-
<TableCell>
210-
<Button
211-
variant="ghost"
212-
size="sm"
213-
className="w-8 h-8 hover:bg-muted"
214-
>
215-
<MoreHorizontal className="h-3 w-3" />
216-
</Button>
272+
<TableCell className="text-sm text-muted-foreground">
273+
{node.totalStorage && node.totalStorage > 0
274+
? `${(node.storageUsage / GB).toFixed(1)}Gi / ${(node.totalStorage / GB).toFixed(1)}Gi`
275+
: "N/A"}
217276
</TableCell>
218277
</TableRow>
219278
))}

src/components/Header.tsx

Lines changed: 10 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,22 @@ interface HeaderProps {
3434
export function Header({ compact = false, mobile = false, podCount/*, pods */}: HeaderProps) { // 2025-09-23 comment out
3535
const { theme, toggleTheme } = useTheme();
3636
//const clusterHealth = useClusterHealth(pods); // 2025-09-23 comment out
37-
const [demoMode, setDemoMode] = useState(false);
38-
const [dashboardIP, setDashboardIP] = useState("192.168.1.10");
37+
// const [demoMode, setDemoMode] = useState(false);
38+
// const [dashboardIP, setDashboardIP] = useState("192.168.1.10");
3939

40-
const handleDemoToggle = async () => {
41-
const newDemoMode = !demoMode;
42-
setDemoMode(newDemoMode);
40+
// const handleDemoToggle = async () => {
41+
// const newDemoMode = !demoMode;
42+
// setDemoMode(newDemoMode);
4343

44-
};
44+
// };
4545

4646
if (mobile) {
4747
return (
4848
<header className="h-16 bg-card/60 backdrop-blur-xl border-b border-border shadow-lg px-4 flex items-center justify-between relative">
4949
{/* Background gradient */}
5050
<div className="absolute inset-0 bg-gradient-to-r from-muted/10 to-muted/20 dark:from-muted/5 dark:to-muted/10"></div>
51-
52-
<div className="flex items-center gap-2 relative z-10">
53-
<Badge className="gap-2 px-2 py-1 bg-slate-50 dark:bg-slate-950 text-slate-700 dark:text-slate-300 border-slate-200 dark:border-slate-800 text-xs">
54-
<div className="w-2 h-2 bg-slate-500 rounded-full"></div>
55-
{podCount || 0} Pods
56-
</Badge>
57-
</div>
58-
59-
<div className="flex items-center gap-2 relative z-10">
51+
52+
<div className="absolute right-6 top-1/2 transform -translate-y-1/2 flex items-center gap-2 lg:gap-4 z-10">
6053
<Button
6154
variant="ghost"
6255
size="sm"
@@ -85,63 +78,8 @@ export function Header({ compact = false, mobile = false, podCount/*, pods */}:
8578
{/* Background gradient */}
8679
<div className="absolute inset-0 bg-gradient-to-r from-muted/10 to-muted/20 dark:from-muted/5 dark:to-muted/10"></div>
8780

88-
<div className="flex items-center gap-3 lg:gap-6 relative z-10 flex-1 min-w-0">
89-
90-
91-
{!compact && (
92-
<div className="flex items-center gap-2 lg:gap-3 min-w-0">
93-
94-
<Badge className="gap-2 px-2 lg:px-3 py-1 lg:py-1.5 bg-orange-500 dark:bg-orange-600 text-white border-orange-600 dark:border-orange-700 hover:bg-orange-600 dark:hover:bg-orange-700 whitespace-nowrap text-xs lg:text-sm hidden sm:flex">
95-
<div className="w-2 lg:w-2.5 h-2 lg:h-2.5 bg-white rounded-full"></div>
96-
{podCount || 0} Pods Running
97-
</Badge>
98-
</div>
99-
)}
100-
101-
102-
103-
{/* Search Bar - Only on larger screens */}
104-
{!compact && (
105-
<div className="relative ml-2 lg:ml-4 hidden xl:block flex-1 max-w-sm">
106-
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-muted-foreground" />
107-
<Input
108-
placeholder="Search resources..."
109-
className="w-full pl-10 pr-12 h-8 lg:h-10 bg-card/80 backdrop-blur-sm border-border/30 shadow-sm hover:shadow-md transition-all text-sm"
110-
/>
111-
<div className="absolute right-3 top-1/2 transform -translate-y-1/2 flex items-center gap-1">
112-
<Command className="w-3 h-3 text-muted-foreground" />
113-
<span className="text-xs text-muted-foreground">K</span>
114-
</div>
115-
</div>
116-
)}
117-
</div>
118-
119-
<div className="flex items-center gap-2 lg:gap-4 relative z-10">
120-
{/* IP Configuration and Demo Toggle */}
121-
{!compact && (
122-
<div className="flex items-center gap-3 hidden lg:flex">
123-
<div className="flex items-center gap-2">
124-
<span className="text-sm font-medium text-foreground whitespace-nowrap">IP Input</span>
125-
<div className="relative">
126-
<Input
127-
placeholder="Dashboard IP..."
128-
value={dashboardIP}
129-
onChange={(e) => setDashboardIP(e.target.value)}
130-
className="w-36 h-9 bg-background border-2 border-primary/20 shadow-lg hover:shadow-xl focus:border-primary focus:shadow-xl transition-all text-sm font-mono font-medium text-[rgba(141,138,138,1)]"
131-
/>
132-
</div>
133-
</div>
134-
<div className="flex items-center gap-3">
135-
<span className="text-sm font-medium text-foreground">Demo</span>
136-
<Switch
137-
checked={demoMode}
138-
onCheckedChange={handleDemoToggle}
139-
className="data-[state=checked]:bg-emerald-500 data-[state=unchecked]:bg-muted scale-125"
140-
/>
141-
</div>
142-
</div>
143-
)}
144-
81+
<div className="flex-1" />
82+
<div className="flex items-center gap-2 lg:gap-4 relative z-10 ml-auto">
14583
<Button
14684
variant="ghost"
14785
size="sm"

0 commit comments

Comments
 (0)