Skip to content

Commit f6232ff

Browse files
authored
Bug fixes and QoL changes (CORS, Docker, Empty states, Server configs, Code cleanup)
Made backend server to listen on all interfaces, added empty state for container & Coolify page, fixed websocket CORS, fixed docker for local development, and removed unused code.
1 parent 1baa694 commit f6232ff

File tree

10 files changed

+473
-639
lines changed

10 files changed

+473
-639
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
# next.js
77
/.next/
88
/out/
9+
/backend/static
10+
11+
# go server build
12+
/backend/server
913

1014
# production
1115
/build

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,18 +162,35 @@ On `amd64` processors, Docker sometimes needs write access to `/proc` to adjust
162162
<br />
163163

164164
## For Developers
165+
165166
To get started with development, you'll need to have [Node.js](https://nodejs.org/) and [bun](https://bun.sh/) installed.
166167

167168
```bash
168169
# Clone the repo
169170
git clone https://github.com/airoflare/statnet.git
170171
cd statnet
171172

172-
# Build and run docker image locally (I fully test using docker locally)
173-
docker build -t metrics . && docker run -p 80:80 -v /var/run/docker.sock:/var/run/docker.sock metrics
173+
# Build the docker imageand run docker image locally (I fully test using docker locally)
174+
docker build -t metrics .
175+
176+
# Run the docker image on port 80
177+
docker run -p 80:80 -v /var/run/docker.sock:/var/run/docker.sock metrics
174178
# → Opens at http://localhost:80
175179
```
176-
180+
181+
<br />
182+
183+
Optional Configuration:
184+
185+
```bash
186+
# If you want to allow other origins like 10.X.X.X or any Ipv4 with localhost you can add it using docker's built in enviroment argument and use the "ALLOWED_CORS_ORIGINS" enviroment variable
187+
188+
#Example:
189+
docker run -p 80:80 -e ALLOWED_CORS_ORIGINS="10.0.0.8" -v /var/run/docker.sock:/var/run/docker.sock metrics
190+
191+
# This will allow you to access the monitor using http://10.0.0.8:80 (10.0.0.8 is an example IP, make sure to use an IP related to the machine that you are running this on)
192+
```
193+
177194
<br />
178195

179196
## Note

app/containers/page.tsx

Lines changed: 126 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -12,80 +12,116 @@ import { ContainerItem } from "@/components/container-item"
1212
import ContainerCard from "@/components/container-card"
1313

1414
export default function ContainersPage() {
15-
const { combinedData } = useWebSocket();
16-
const [totalCpuData, setTotalCpuData] = useState<DataPoint[]>([{ value: 0, timestamp: Date.now() - 1000 }, { value: 0, timestamp: Date.now() }])
17-
const [totalRamData, setTotalRamData] = useState<DataPoint[]>([{ value: 0, timestamp: Date.now() - 1000 }, { value: 0, timestamp: Date.now() }])
18-
const [totalNetworkRxData, setTotalNetworkRxData] = useState<DataPoint[]>([{ value: 0, timestamp: Date.now() - 1000 }, { value: 0, timestamp: Date.now() }])
19-
const [totalNetworkTxData, setTotalNetworkTxData] = useState<DataPoint[]>([{ value: 0, timestamp: Date.now() - 1000 }, { value: 0, timestamp: Date.now() }])
15+
const { combinedData } = useWebSocket()
16+
const [totalCpuData, setTotalCpuData] = useState<DataPoint[]>([])
17+
const [totalRamData, setTotalRamData] = useState<DataPoint[]>([])
18+
const [totalNetworkRxData, setTotalNetworkRxData] = useState<DataPoint[]>([])
19+
const [totalNetworkTxData, setTotalNetworkTxData] = useState<DataPoint[]>([])
2020
const [containers, setContainers] = useState<Container[]>([])
2121

2222
const [totalMemory, setTotalMemory] = useState(0);
2323

2424
const [isExpanded, setIsExpanded] = useState(false)
2525

2626
useEffect(() => {
27-
const maxPoints = 7; // Keep last 8 data points for the graph
28-
29-
if (combinedData) {
30-
// Sort containers: running first (by RAM usage), then others by name
31-
const sortedContainers = [...combinedData.containers].sort((a, b) => {
32-
// Prioritize running containers
33-
const aIsRunning = a.status === "running";
34-
const bIsRunning = b.status === "running";
35-
36-
if (aIsRunning && !bIsRunning) return -1; // a comes before b
37-
if (!aIsRunning && bIsRunning) return 1; // b comes before a
38-
39-
// If both are running, sort by RAM usage (descending), then by ID for stability
40-
if (aIsRunning && bIsRunning) {
41-
const aRam = a.ramUsage && a.ramUsage.length > 0 ? a.ramUsage[a.ramUsage.length - 1].value : 0;
42-
const bRam = b.ramUsage && b.ramUsage.length > 0 ? b.ramUsage[b.ramUsage.length - 1].value : 0;
43-
if (aRam !== bRam) {
44-
return bRam - aRam;
45-
}
46-
return a.id.localeCompare(b.id); // Stable sort by ID
47-
}
48-
49-
// If both are not running (stopped, exited, restarting), sort by display name, then by ID for stability
50-
const nameComparison = (a.name || '').localeCompare(b.name || '');
51-
if (nameComparison !== 0) {
52-
return nameComparison;
53-
}
54-
return a.id.localeCompare(b.id); // Stable sort by ID
55-
});
56-
27+
const maxPoints = 7;
28+
const now = Date.now();
29+
30+
// Initialize with proper default values if empty
31+
if (totalCpuData.length === 0) {
32+
setTotalCpuData([
33+
{ value: 0, timestamp: now - 1000 },
34+
{ value: 0, timestamp: now }
35+
]);
36+
}
37+
if (totalRamData.length === 0) {
38+
setTotalRamData([
39+
{ value: 0, timestamp: now - 1000 },
40+
{ value: 0, timestamp: now }
41+
]);
42+
}
43+
if (totalNetworkRxData.length === 0) {
44+
setTotalNetworkRxData([
45+
{ value: 0, timestamp: now - 1000 },
46+
{ value: 0, timestamp: now }
47+
]);
48+
}
49+
if (totalNetworkTxData.length === 0) {
50+
setTotalNetworkTxData([
51+
{ value: 0, timestamp: now - 1000 },
52+
{ value: 0, timestamp: now }
53+
]);
54+
}
5755

58-
setContainers(sortedContainers);
56+
if (!combinedData || !combinedData.containers) {
57+
// Set default values if no data (Safe fallback)
58+
setContainers([]);
59+
setTotalCpuData(prev => [...prev].slice(-maxPoints));
60+
setTotalRamData(prev => [...prev].slice(-maxPoints));
61+
setTotalNetworkRxData(prev => [...prev].slice(-maxPoints));
62+
setTotalNetworkTxData(prev => [...prev].slice(-maxPoints));
63+
return;
64+
}
5965

60-
// Update total system memory for scaling
61-
if (combinedData.systemInfo && combinedData.systemInfo.resourceData && combinedData.systemInfo.resourceData.memory && combinedData.systemInfo.resourceData.memory.length > 0) {
62-
setTotalMemory(combinedData.systemInfo.resourceData.memory[0].totalMemory);
66+
// Sort containers: running first (by RAM usage), then others by name
67+
const sortedContainers = [...combinedData.containers].sort((a, b) => {
68+
// Prioritize running containers
69+
const aIsRunning = a.status === "running";
70+
const bIsRunning = b.status === "running";
71+
72+
if (aIsRunning && !bIsRunning) return -1; // a comes before b
73+
if (!aIsRunning && bIsRunning) return 1; // b comes before a
74+
75+
// If both are running, sort by RAM usage (descending)
76+
if (aIsRunning && bIsRunning) {
77+
const aRam = a.ramUsage?.length > 0 ? a.ramUsage[a.ramUsage.length - 1]?.value || 0 : 0;
78+
const bRam = b.ramUsage?.length > 0 ? b.ramUsage[b.ramUsage.length - 1]?.value || 0 : 0;
79+
if (aRam !== bRam) {
80+
return bRam - aRam;
81+
}
6382
}
6483

65-
// Calculate total CPU and RAM for containers
66-
const currentTotalCpu = combinedData.containers.reduce((sum, c) => sum + (c.cpuUsage[c.cpuUsage.length - 1]?.value || 0), 0);
67-
const currentTotalRam = combinedData.containers.reduce((sum, c) => sum + (c.ramUsage[c.ramUsage.length - 1]?.value || 0), 0);
68-
69-
setTotalCpuData(prev => [...prev, { value: currentTotalCpu, timestamp: Date.now() }].slice(-maxPoints));
70-
setTotalRamData(prev => [...prev, { value: currentTotalRam, timestamp: Date.now() }].slice(-maxPoints));
71-
72-
const currentTotalNetworkRx = combinedData.containers.reduce((sum, c) => sum + (c.networkRxBytes[c.networkRxBytes.length - 1]?.value || 0), 0);
73-
const currentTotalNetworkTx = combinedData.containers.reduce((sum, c) => sum + (c.networkTxBytes[c.networkTxBytes.length - 1]?.value || 0), 0);
74-
75-
setTotalNetworkRxData(prev => [...prev, { value: currentTotalNetworkRx, timestamp: Date.now() }].slice(-maxPoints));
76-
setTotalNetworkTxData(prev => [...prev, { value: currentTotalNetworkTx, timestamp: Date.now() }].slice(-maxPoints));
77-
78-
// Update total system memory for scaling
79-
if (combinedData.systemInfo && combinedData.systemInfo.resourceData && combinedData.systemInfo.resourceData.memory && combinedData.systemInfo.resourceData.memory.length > 0) {
80-
setTotalMemory(combinedData.systemInfo.resourceData.memory[0].totalMemory);
81-
}
84+
// Sort by name as fallback
85+
return (a.name || '').localeCompare(b.name || '');
86+
});
87+
88+
setContainers(sortedContainers);
89+
90+
// Calculate totals
91+
const currentTotalCpu = combinedData.containers.reduce((sum, c) =>
92+
sum + ((c.cpuUsage && c.cpuUsage.length > 0) ? (c.cpuUsage[c.cpuUsage.length - 1]?.value || 0) : 0), 0);
93+
94+
const currentTotalRam = combinedData.containers.reduce((sum, c) =>
95+
sum + ((c.ramUsage && c.ramUsage.length > 0) ? (c.ramUsage[c.ramUsage.length - 1]?.value || 0) : 0), 0);
96+
97+
const currentTotalNetworkRx = combinedData.containers.reduce((sum, c) =>
98+
sum + ((c.networkRxBytes && c.networkRxBytes.length > 0) ? (c.networkRxBytes[c.networkRxBytes.length - 1]?.value || 0) : 0), 0);
99+
100+
const currentTotalNetworkTx = combinedData.containers.reduce((sum, c) =>
101+
sum + ((c.networkTxBytes && c.networkTxBytes.length > 0) ? (c.networkTxBytes[c.networkTxBytes.length - 1]?.value || 0) : 0), 0);
102+
103+
setTotalCpuData(prev => [...prev, { value: currentTotalCpu, timestamp: Date.now() }].slice(-maxPoints));
104+
setTotalRamData(prev => [...prev, { value: currentTotalRam, timestamp: Date.now() }].slice(-maxPoints));
105+
setTotalNetworkRxData(prev => [...prev, { value: currentTotalNetworkRx, timestamp: Date.now() }].slice(-maxPoints));
106+
setTotalNetworkTxData(prev => [...prev, { value: currentTotalNetworkTx, timestamp: Date.now() }].slice(-maxPoints));
107+
108+
// Update total system memory for scaling
109+
if (combinedData.systemInfo?.resourceData?.memory?.[0]?.totalMemory) {
110+
setTotalMemory(combinedData.systemInfo.resourceData.memory[0].totalMemory);
82111
}
83-
}, [combinedData]);
112+
}, [combinedData, totalCpuData.length, totalRamData.length, totalNetworkRxData.length, totalNetworkTxData.length]);
84113

85-
const currentTotalCpu = totalCpuData[totalCpuData.length - 1]?.value || 0
86-
const currentTotalRam = totalRamData[totalRamData.length - 1]?.value || 0
87-
const currentTotalNetworkRx = totalNetworkRxData[totalNetworkRxData.length - 1]?.value || 0;
88-
const currentTotalNetworkTx = totalNetworkTxData[totalNetworkTxData.length - 1]?.value || 0;
114+
// Safe data access
115+
const currentTotalCpu = totalCpuData[totalCpuData.length - 1]?.value ?? 0;
116+
const currentTotalRam = totalRamData[totalRamData.length - 1]?.value ?? 0;
117+
const currentTotalNetworkRx = totalNetworkRxData[totalNetworkRxData.length - 1]?.value ?? 0;
118+
const currentTotalNetworkTx = totalNetworkTxData[totalNetworkTxData.length - 1]?.value ?? 0;
119+
120+
// Ensure data arrays are never null for ResourceCard components
121+
const safeNetworkRxData = totalNetworkRxData || [];
122+
const safeNetworkTxData = totalNetworkTxData || [];
123+
const safeCpuData = totalCpuData || [];
124+
const safeRamData = totalRamData || [];
89125

90126
const formattedTotalRamValue = formatMemorySize(currentTotalRam);
91127

@@ -119,35 +155,35 @@ export default function ContainersPage() {
119155
</div>
120156

121157
<div className="grid grid-cols-1 sm:grid-cols-2 gap-1.5">
122-
<ResourceCard icon={Cog} label="Compute" value={currentTotalCpu} data={totalCpuData} color="#1e90ff" maxValue={100} />
158+
<ResourceCard icon={Cog} label="Compute" value={currentTotalCpu} data={safeCpuData} color="#1e90ff" maxValue={100} />
123159
<ResourceCard
124160
icon={Layers}
125161
label="Memory"
126162
value={currentTotalRam}
127-
data={totalRamData.map(d => ({ value: d.value, timestamp: d.timestamp }))}
163+
data={safeRamData}
128164
color="#00ced1"
129165
valueDisplay={formattedTotalRamValue}
130-
maxValue={totalMemory} // Pass totalMemory for scaling
166+
maxValue={totalMemory || 1}
131167
/>
132168
<ResourceCard
133169
icon={ArrowDown}
134170
label="Inbound"
135171
value={currentTotalNetworkRx}
136-
data={totalNetworkRxData}
172+
data={safeNetworkRxData}
137173
color="#8b5cf6"
138174
unit="B/s"
139175
valueDisplay={formatNetworkSpeed(currentTotalNetworkRx)}
140-
maxValue={Math.max(...totalNetworkRxData.map(d => d.value), ...totalNetworkTxData.map(d => d.value), 1) * 1.15}
176+
maxValue={Math.max(...safeNetworkRxData.map(d => d?.value ?? 0), ...safeNetworkTxData.map(d => d?.value ?? 0), 1)}
141177
/>
142178
<ResourceCard
143179
icon={ArrowUp}
144180
label="Outbound"
145181
value={currentTotalNetworkTx}
146-
data={totalNetworkTxData}
182+
data={safeNetworkTxData}
147183
color="#8b5cf6"
148184
unit="B/s"
149185
valueDisplay={formatNetworkSpeed(currentTotalNetworkTx)}
150-
maxValue={Math.max(...totalNetworkRxData.map(d => d.value), ...totalNetworkTxData.map(d => d.value), 1) * 1.15}
186+
maxValue={Math.max(...safeNetworkRxData.map(d => d?.value ?? 0), ...safeNetworkTxData.map(d => d?.value ?? 0), 1)}
151187
/>
152188
</div>
153189
</motion.div>
@@ -176,30 +212,43 @@ export default function ContainersPage() {
176212
</AnimatePresence>
177213
</Card>
178214

179-
{/* Display individual container cards */}
180-
{combinedData && combinedData.containers.length > 0 && (
215+
{/* Display individual container cards with safety checks */}
216+
{(combinedData?.containers || []).length > 0 ? (
181217
<motion.div
182218
className="flex flex-col items-center justify-start w-full space-y-4 pt-4"
183219
initial={{ opacity: 0 }}
184220
animate={{ opacity: 1 }}
185221
transition={{ duration: 0.5 }}
186222
>
187-
{combinedData.containers
188-
223+
{(combinedData?.containers || [])
189224
.sort((a, b) => {
190-
// Sort by status: running first, then others
191225
const statusOrder = { "running": 0, "restarting": 1, "stopped": 2, "exited": 3 };
192-
const statusComparison = statusOrder[a.status] - statusOrder[b.status];
226+
const statusComparison = (statusOrder[a?.status] ?? 999) - (statusOrder[b?.status] ?? 999);
193227
if (statusComparison !== 0) {
194228
return statusComparison;
195229
}
196-
// Then sort by name ascending
197-
return a.name.localeCompare(b.name);
230+
return ((a?.name || '') || '').localeCompare((b?.name || '') || '');
198231
})
199232
.map((container) => (
200-
<ContainerCard key={container.id} container={container} />
233+
<ContainerCard
234+
key={container?.id || Math.random().toString()}
235+
container={container}
236+
/>
201237
))}
202238
</motion.div>
239+
) : (
240+
<motion.div
241+
className="flex flex-col items-center justify-center w-full p-8 text-center"
242+
initial={{ opacity: 0 }}
243+
animate={{ opacity: 1 }}
244+
transition={{ duration: 0.5 }}
245+
>
246+
<Package className="w-12 h-12 text-muted-foreground mb-4" />
247+
<h3 className="text-lg font-medium mb-2">No Containers Found</h3>
248+
<p className="text-sm text-muted-foreground">
249+
There are currently no Docker containers to display.
250+
</p>
251+
</motion.div>
203252
)}
204253
</motion.div>
205254
)

0 commit comments

Comments
 (0)