Skip to content

Commit 932f364

Browse files
author
secus
committed
Refactor logging handlers and Kubernetes service
1 parent 604eb6d commit 932f364

8 files changed

Lines changed: 677 additions & 70 deletions

File tree

.env.development

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ API_KEY_PREFIX=ce_dev_
1919
KUBERNETES_NAMESPACE=container-engine-dev
2020

2121
# Domain Configuration
22-
DOMAIN_SUFFIX=vinhomes.co.uk
22+
DOMAIN_SUFFIX=your_domain_suffix
2323

2424
# Email Configuration - Mailtrap
25-
MAILTRAP_SMTP_HOST=live.smtp.mailtrap.io
25+
MAILTRAP_SMTP_HOST=your_host
2626
MAILTRAP_SMTP_PORT=587
2727
MAILTRAP_USERNAME=your_mailtrap_username
2828
MAILTRAP_PASSWORD=your_mailtrap_password
29-
EMAIL_FROM=noreply@vinhomes.co.uk
30-
EMAIL_FROM_NAME=Container Engine
29+
EMAIL_FROM=your_email
30+
EMAIL_FROM_NAME=your_app_name
3131

3232
# Logging
3333
RUST_LOG=container_engine=debug,tower_http=debug
34-
KUBECONFIG_PATH=./k8sConfig.yaml
34+
KUBECONFIG_PATH=./k8sConfig.yaml

apps/container-engine-frontend/src/api/api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import axios from 'axios';
22

33
// Base API configuration
4-
// const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || window.location.origin;
5-
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "http://localhost:3000";
4+
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || window.location.origin;
5+
// const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "http://localhost:3000";
66

77

88
// Create axios instance with default config

apps/container-engine-frontend/src/components/DeploymentDetail/LogsPage.tsx

Lines changed: 142 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
// LogsPage.jsx
22
import { useState, useEffect, useRef } from 'react';
3-
import { ClipboardDocumentListIcon } from "@heroicons/react/24/outline";
3+
import { ClipboardDocumentListIcon, CubeIcon } from "@heroicons/react/24/outline";
44
import { useParams } from 'react-router-dom';
55
import api from '../../api/api';
66

77
export default function LogsPage() {
88
const { deploymentId } = useParams();
99
const [logs, setLogs] = useState<any[]>([]);
10+
const [pods, setPods] = useState([]);
11+
const [selectedPod, setSelectedPod] = useState('all'); // 'all' or specific pod name
1012
const [isConnected, setIsConnected] = useState(false);
1113
const [isConnecting, setIsConnecting] = useState(false);
1214
const [isLoadingHistory, setIsLoadingHistory] = useState(false);
15+
const [isLoadingPods, setIsLoadingPods] = useState(false);
1316
const [error, setError] = useState<any>(null);
14-
const wsRef: any = useRef(null);
17+
const wsRef = useRef<any>(null);
1518
const logsEndRef: any = useRef(null);
16-
const reconnectTimeoutRef: any = useRef(null);
19+
const reconnectTimeoutRef = useRef<any>(null);
1720
const reconnectDelay = useRef(1000);
1821

1922
// Auto-scroll to bottom when new logs arrive
@@ -36,6 +39,29 @@ export default function LogsPage() {
3639
return null;
3740
};
3841

42+
// Get WebSocket URL with proper protocol
43+
const getWebSocketUrl = () => {
44+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
45+
const host = window.location.host;
46+
return `${protocol}//${host}`;
47+
};
48+
49+
// Load pods list
50+
const loadPods = async () => {
51+
if (!deploymentId) return;
52+
53+
setIsLoadingPods(true);
54+
try {
55+
const response = await api.get(`/v1/deployments/${deploymentId}/pods`);
56+
setPods(response.data.pods || []);
57+
} catch (err) {
58+
console.error('Failed to load pods:', err);
59+
// Don't set error for pods loading failure, just log it
60+
} finally {
61+
setIsLoadingPods(false);
62+
}
63+
};
64+
3965
// Load historical logs from API
4066
const loadHistoricalLogs = async (retryCount = 0) => {
4167
if (!deploymentId) return;
@@ -44,7 +70,14 @@ export default function LogsPage() {
4470
setError(null);
4571

4672
try {
47-
const response = await api.get(`/v1/deployments/${deploymentId}/logs?tail=100`);
73+
let endpoint;
74+
if (selectedPod === 'all') {
75+
endpoint = `/v1/deployments/${deploymentId}/logs?tail=100`;
76+
} else {
77+
endpoint = `/v1/deployments/${deploymentId}/pods/${selectedPod}/logs?tail=100`;
78+
}
79+
80+
const response = await api.get(endpoint);
4881

4982
if (response.data.logs) {
5083
// Parse historical logs - assuming they come as a single string with newlines
@@ -62,7 +95,8 @@ export default function LogsPage() {
6295
timestamp,
6396
message: line,
6497
id: `history-${index}`,
65-
isHistorical: true
98+
isHistorical: true,
99+
podName: selectedPod === 'all' ? 'merged' : selectedPod
66100
};
67101
});
68102

@@ -113,8 +147,18 @@ export default function LogsPage() {
113147
setIsConnecting(true);
114148
setError(null);
115149

116-
// Add token to WebSocket URL as query parameter
117-
const wsUrl = `ws://${window.location.host}/v1/deployments/${deploymentId}/logs/stream?tail=50&token=${encodeURIComponent('Bearer ' + token)}`;
150+
// Build WebSocket URL based on selected pod with proper protocol
151+
const baseWsUrl = getWebSocketUrl();
152+
let wsUrl;
153+
154+
if (selectedPod === 'all') {
155+
wsUrl = `${baseWsUrl}/v1/deployments/${deploymentId}/logs/stream?tail=50&token=${encodeURIComponent('Bearer ' + token)}`;
156+
} else {
157+
wsUrl = `${baseWsUrl}/v1/deployments/${deploymentId}/pods/${selectedPod}/logs/ws?tail=50&token=${encodeURIComponent('Bearer ' + token)}`;
158+
}
159+
160+
console.log('Connecting to WebSocket:', wsUrl.replace(/token=[^&]+/, 'token=***')); // Log URL without token
161+
118162
const ws = new WebSocket(wsUrl);
119163

120164
ws.onopen = () => {
@@ -125,10 +169,11 @@ export default function LogsPage() {
125169
};
126170

127171
ws.onmessage = (event) => {
128-
console.log('WebSocket message received:', event);
129172
// Skip connection confirmation messages
130173
if (event.data === 'Connected to log stream' ||
174+
event.data.includes('Connected to log stream for pod:') ||
131175
event.data === 'Log stream ended' ||
176+
event.data.includes('Log stream ended for pod:') ||
132177
event.data.includes('Authentication')) {
133178
return;
134179
}
@@ -138,10 +183,11 @@ export default function LogsPage() {
138183
timestamp,
139184
message: event.data,
140185
id: `live-${timestamp}-${Math.random()}`,
141-
isHistorical: false
186+
isHistorical: false,
187+
podName: selectedPod === 'all' ? 'merged' : selectedPod
142188
};
143189

144-
setLogs((prev: any) => [...prev, newLog]);
190+
setLogs(prev => [...prev, newLog]);
145191
};
146192

147193
ws.onerror = (error) => {
@@ -150,7 +196,7 @@ export default function LogsPage() {
150196
};
151197

152198
ws.onclose = (event) => {
153-
console.log('WebSocket disconnected', event.code, event.reason);
199+
console.log(`WebSocket disconnected: ${event.code} ${event.reason || ''}`);
154200
setIsConnected(false);
155201
setIsConnecting(false);
156202
wsRef.current = null;
@@ -171,6 +217,7 @@ export default function LogsPage() {
171217
const delay = reconnectDelay.current;
172218
reconnectDelay.current = Math.min(delay * 2, 30000); // Max 30s
173219

220+
console.log(`Attempting to reconnect in ${delay}ms (attempt ${Math.log2(delay / 1000) + 1})`);
174221
setError(`Disconnected. Reconnecting in ${delay / 1000}s...`);
175222

176223
reconnectTimeoutRef.current = setTimeout(() => {
@@ -181,9 +228,25 @@ export default function LogsPage() {
181228
wsRef.current = ws;
182229
};
183230

184-
// Initialize: Load history first, then connect WebSocket
231+
// Handle pod selection change
232+
const handlePodChange = (podName: any) => {
233+
if (podName === selectedPod) return;
234+
235+
// Disconnect current WebSocket
236+
if (wsRef.current) {
237+
wsRef.current.close();
238+
}
239+
240+
setSelectedPod(podName);
241+
setLogs([]); // Clear current logs
242+
};
243+
244+
// Initialize: Load pods and history first, then connect WebSocket
185245
useEffect(() => {
186246
if (deploymentId) {
247+
// Load pods first
248+
loadPods();
249+
187250
// Load historical logs first
188251
loadHistoricalLogs().then(() => {
189252
// Small delay to show historical logs before connecting WebSocket
@@ -204,15 +267,26 @@ export default function LogsPage() {
204267
};
205268
}, [deploymentId]);
206269

270+
// Re-load logs when pod selection changes
271+
useEffect(() => {
272+
if (deploymentId && selectedPod) {
273+
loadHistoricalLogs().then(() => {
274+
setTimeout(() => {
275+
connectWebSocket();
276+
}, 500);
277+
});
278+
}
279+
}, [selectedPod]);
280+
207281
// Manual refresh - reload everything
208282
const handleRefresh = async () => {
209283
setLogs([]); // Clear logs
210284
if (wsRef.current) {
211285
wsRef.current.close();
212286
}
213287

214-
// Reload historical logs then reconnect WebSocket
215-
await loadHistoricalLogs();
288+
// Reload pods and historical logs then reconnect WebSocket
289+
await Promise.all([loadPods(), loadHistoricalLogs()]);
216290
setTimeout(() => {
217291
connectWebSocket();
218292
}, 500);
@@ -225,15 +299,16 @@ export default function LogsPage() {
225299

226300
// Download logs
227301
const handleDownload = () => {
228-
const logText = logs.map((log) =>
302+
const logText = logs.map(log =>
229303
`[${new Date(log.timestamp).toLocaleString()}] ${log.message}`
230304
).join('\n');
231305

232306
const blob = new Blob([logText], { type: 'text/plain' });
233307
const url = URL.createObjectURL(blob);
234308
const a = document.createElement('a');
235309
a.href = url;
236-
a.download = `logs-${deploymentId}-${new Date().toISOString().split('T')[0]}.txt`;
310+
const podSuffix = selectedPod === 'all' ? 'all-pods' : selectedPod;
311+
a.download = `logs-${deploymentId}-${podSuffix}-${new Date().toISOString().split('T')[0]}.txt`;
237312
a.click();
238313
URL.revokeObjectURL(url);
239314
};
@@ -267,7 +342,7 @@ export default function LogsPage() {
267342
return (
268343
<div className="bg-white rounded-2xl shadow-lg border border-gray-100 overflow-hidden">
269344
<div className="px-8 py-6 bg-gray-50 border-b border-gray-200">
270-
<div className="flex items-center justify-between">
345+
<div className="flex items-center justify-between mb-4">
271346
<div className="flex items-center">
272347
<div className="w-12 h-12 bg-gray-100 rounded-xl flex items-center justify-center mr-4">
273348
<ClipboardDocumentListIcon className="h-6 w-6 text-gray-600" />
@@ -308,6 +383,44 @@ export default function LogsPage() {
308383
</div>
309384
</div>
310385

386+
{/* Pod Selection */}
387+
<div className="flex items-center space-x-4">
388+
<div className="flex items-center space-x-2">
389+
<CubeIcon className="h-5 w-5 text-gray-500" />
390+
<span className="text-sm font-medium text-gray-700">View logs from:</span>
391+
</div>
392+
<div className="flex flex-wrap gap-2">
393+
<button
394+
onClick={() => handlePodChange('all')}
395+
disabled={isLoadingPods}
396+
className={`px-3 py-1 rounded-lg text-sm font-medium transition-all ${selectedPod === 'all'
397+
? 'bg-blue-600 text-white'
398+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
399+
} disabled:opacity-50 disabled:cursor-not-allowed`}
400+
>
401+
All Pods (Merged)
402+
</button>
403+
{pods.map((pod: any) => (
404+
<button
405+
key={pod.name}
406+
onClick={() => handlePodChange(pod.name)}
407+
disabled={isLoadingPods}
408+
className={`px-3 py-1 rounded-lg text-sm font-medium transition-all flex items-center space-x-1 ${selectedPod === pod.name
409+
? 'bg-blue-600 text-white'
410+
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
411+
} disabled:opacity-50 disabled:cursor-not-allowed`}
412+
>
413+
<span>{pod.name}</span>
414+
<span className={`w-2 h-2 rounded-full ${pod.ready ? 'bg-green-400' : 'bg-red-400'
415+
}`}></span>
416+
</button>
417+
))}
418+
{isLoadingPods && (
419+
<div className="px-3 py-1 text-sm text-gray-500">Loading pods...</div>
420+
)}
421+
</div>
422+
</div>
423+
311424
{error && (
312425
<div className="mt-3 text-sm text-red-600 bg-red-50 px-3 py-2 rounded-lg">
313426
{error}
@@ -319,14 +432,19 @@ export default function LogsPage() {
319432
<div className="bg-gray-900 text-white font-mono text-sm p-6 h-96 overflow-y-auto custom-scrollbar">
320433
{logs.length > 0 ? (
321434
<>
322-
{logs.map((log) => (
435+
{logs.map(log => (
323436
<div key={log.id} className="flex space-x-4 py-1 hover:bg-gray-800 px-2 rounded group">
324437
<span className="text-gray-500 flex-shrink-0 select-none">
325438
{new Date(log.timestamp).toLocaleTimeString()}
326439
</span>
327440
<span className={`flex-shrink-0 select-none ${log.isHistorical ? 'text-gray-600' : 'text-green-400'}`}>
328441
{log.isHistorical ? '◦' : '│'}
329442
</span>
443+
{selectedPod === 'all' && log.podName !== 'merged' && (
444+
<span className="text-blue-400 flex-shrink-0 select-none text-xs">
445+
[{log.podName}]
446+
</span>
447+
)}
330448
<span className="flex-1 break-all whitespace-pre-wrap">{log.message}</span>
331449
</div>
332450
))}
@@ -339,7 +457,10 @@ export default function LogsPage() {
339457
{isLoadingHistory || isConnecting ? 'Loading logs...' : 'No logs available at the moment.'}
340458
</p>
341459
<p className="text-gray-600 text-sm mt-2">
342-
Logs will appear here once your application starts generating them.
460+
{selectedPod === 'all'
461+
? 'Logs from all pods will appear here once your application starts generating them.'
462+
: `Logs from pod "${selectedPod}" will appear here once it starts generating them.`
463+
}
343464
</p>
344465
</div>
345466
)}
@@ -363,6 +484,7 @@ export default function LogsPage() {
363484
<div className="px-6 py-2 bg-gray-50 border-t border-gray-200 text-xs text-gray-500 flex justify-between">
364485
<span>
365486
{logs.filter(log => log.isHistorical).length} historical + {logs.filter(log => !log.isHistorical).length} live logs
487+
{selectedPod !== 'all' && ` from ${selectedPod}`}
366488
</span>
367489
<span>Total: {logs.length} lines</span>
368490
</div>
@@ -386,4 +508,4 @@ export default function LogsPage() {
386508
`}</style>
387509
</div>
388510
);
389-
}
511+
}

0 commit comments

Comments
 (0)