Skip to content

Commit 109953c

Browse files
authored
Merge pull request #142 from oslabs-beta/user0824/feat/dashboard
feat: update dashboard components and notification service. implement…
2 parents d0f3eef + fd7a96c commit 109953c

File tree

3 files changed

+119
-6
lines changed

3 files changed

+119
-6
lines changed

client/src/App.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Header from './components/Header';
1111
import { SignedIn, SignedOut } from '@clerk/clerk-react';
1212
import { Routes, Route } from 'react-router-dom';
1313
import { ClusterProvider } from './contexts/ClusterContext';
14+
import { Toaster } from './components/ui/sonner';
1415

1516
const App = () => {
1617
const [isDark, setIsDark] = useState(() => {
@@ -64,6 +65,15 @@ const App = () => {
6465
{/* Scrollable content area with top padding to account for header */}
6566
<div className="flex-1 overflow-y-auto pb-24 pt-16">
6667
<div className="max-w-9xl mx-auto">
68+
{/* SONNER TOAST NOTIFICATIONS */}
69+
<Toaster
70+
theme={isDark ? 'dark' : 'light'}
71+
position="bottom-right"
72+
richColors={true}
73+
expand={false}
74+
duration={5000}
75+
closeButton={false}
76+
/>
6777
<Routes>
6878
<Route path="/" element={<Dashboard />} />
6979
<Route path="/historical" element={<HistoricalData />} />

client/src/hooks/useNotifications.tsx

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useState } from 'react';
22
import { io } from 'socket.io-client';
3+
import { toast } from 'sonner';
34

45
export interface K8sNotification {
56
id: string;
@@ -22,6 +23,51 @@ export const useNotifications = () => {
2223
const [notifications, setNotifications] = useState<K8sNotification[]>([]);
2324
const [isConnected, setIsConnected] = useState(false);
2425

26+
// Simple function to show Sonner toast - let Sonner handle all styling
27+
const showToast = (notification: K8sNotification) => {
28+
const { type, title, message, metadata, similarEventsCount } = notification;
29+
30+
// Create simple message content
31+
const toastMessage = `${title}\n${metadata.namespace}/${metadata.pod}\n${message}${
32+
similarEventsCount && similarEventsCount > 0
33+
? `\n${similarEventsCount} similar events found`
34+
: ''
35+
}`;
36+
37+
// Let Sonner handle the colors and styling based on type
38+
switch (type) {
39+
case 'error':
40+
if (metadata.severity === 'critical') {
41+
toast.error(toastMessage, {
42+
duration: 10000,
43+
action: {
44+
label: 'View Details',
45+
onClick: () => {
46+
console.log('Opening notification details:', notification.id);
47+
},
48+
},
49+
});
50+
} else {
51+
toast.error(toastMessage, {
52+
duration: 7000,
53+
});
54+
}
55+
break;
56+
57+
case 'warning':
58+
toast.warning(toastMessage, {
59+
duration: 5000,
60+
});
61+
break;
62+
63+
default:
64+
toast.info(toastMessage, {
65+
duration: 4000,
66+
});
67+
break;
68+
}
69+
};
70+
2571
useEffect(() => {
2672
const socketInstance = io(
2773
import.meta.env.VITE_API_URL || 'http://localhost:3000',
@@ -30,19 +76,31 @@ export const useNotifications = () => {
3076
socketInstance.on('connect', () => {
3177
setIsConnected(true);
3278
console.log('[WebSocket] Connected to server');
79+
80+
// Simple connection success toast
81+
toast.success('Connected to Kubernetes monitoring');
3382
});
3483

3584
socketInstance.on('disconnect', () => {
3685
setIsConnected(false);
3786
console.log('[WebSocket] Disconnected from server');
87+
88+
// Simple disconnection warning toast
89+
toast.warning('Disconnected from monitoring server');
3890
});
3991

4092
socketInstance.on('k8s-notification', (notification: K8sNotification) => {
4193
console.log('[WebSocket] Received notification:', notification);
94+
95+
const newNotification = { ...notification, isRead: false };
96+
4297
setNotifications((prev) => [
43-
{ ...notification, isRead: false },
98+
newNotification,
4499
...prev.slice(0, 49), // Keep only latest 50 notifications
45100
]);
101+
102+
// Show Sonner toast notification
103+
showToast(newNotification);
46104
});
47105

48106
return () => {
@@ -72,6 +130,7 @@ export const useNotifications = () => {
72130

73131
const clearAllNotifications = () => {
74132
setNotifications([]);
133+
toast.success('All notifications cleared');
75134
};
76135

77136
const unreadCount = notifications.filter((n) => !n.isRead).length;

server/src/services/notificationService.ts

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,24 @@ interface K8sEvent {
1414
timestamp: string;
1515
}
1616

17+
// Extended notification interface for better toast control
18+
interface ExtendedK8sNotification extends K8sNotification {
19+
toastConfig?: {
20+
duration?: number;
21+
dismissible?: boolean;
22+
showAction?: boolean;
23+
priority?: 'low' | 'medium' | 'high' | 'critical';
24+
};
25+
}
26+
1727
export const sendNotifications = async (
1828
event: K8sEvent,
1929
aiResponse: string,
2030
similarEvents: any,
2131
) => {
2232
try {
23-
// Create unified notification object
24-
const notification: K8sNotification = {
33+
// Create unified notification object with toast configuration
34+
const notification: ExtendedK8sNotification = {
2535
id: uuidv4(),
2636
type: determineNotificationType(event.reason),
2737
title: `Kubernetes ${event.reason} Detected`,
@@ -35,11 +45,26 @@ export const sendNotifications = async (
3545
},
3646
aiAnalysis: aiResponse,
3747
similarEventsCount: similarEvents.matches?.length || 0,
48+
// Toast-specific configuration
49+
toastConfig: {
50+
duration: getToastDuration(event.reason),
51+
dismissible: true,
52+
showAction: shouldShowAction(event.reason),
53+
priority: determineSeverity(event.reason) as
54+
| 'low'
55+
| 'medium'
56+
| 'high'
57+
| 'critical',
58+
},
3859
};
3960

40-
// Send to dashboard via WebSocket
61+
// Send to dashboard via WebSocket (includes toast data)
4162
broadcastNotification(notification);
42-
console.log(chalk.green('[Notifications] Sent to dashboard via WebSocket'));
63+
console.log(
64+
chalk.green(
65+
'[Notifications] Sent to dashboard via WebSocket with toast config',
66+
),
67+
);
4368

4469
// Send to Slack (existing functionality)
4570
const slackMessage = formatSlackMessage(event, aiResponse, similarEvents);
@@ -76,6 +101,21 @@ const determineSeverity = (
76101
return 'low';
77102
};
78103

104+
// Helper function to determine toast duration based on event type
105+
const getToastDuration = (reason: string): number => {
106+
if (reason.includes('OOMKilled')) return 15000; // 15 seconds for critical
107+
if (reason.includes('CrashLoopBackOff')) return 10000; // 10 seconds for high
108+
if (reason.includes('Failed')) return 7000; // 7 seconds for medium
109+
return 5000; // 5 seconds for low priority
110+
};
111+
112+
// Helper function to determine if toast should show action button
113+
const shouldShowAction = (reason: string): boolean => {
114+
// Show action button for events that typically require immediate attention
115+
const criticalReasons = ['OOMKilled', 'CrashLoopBackOff', 'Failed'];
116+
return criticalReasons.some((r) => reason.includes(r));
117+
};
118+
79119
const formatSlackMessage = (
80120
event: K8sEvent,
81121
aiResponse: string,
@@ -99,5 +139,9 @@ ${event.message}
99139
*AI Analysis and Resolution:*
100140
${aiResponse}
101141
102-
${similarEventCount > 0 ? `_Note: Found ${similarEventCount} similar past events that informed this analysis._` : '_Note: No similar past events found in the database._'}`;
142+
${
143+
similarEventCount > 0
144+
? `_Note: Found ${similarEventCount} similar past events that informed this analysis._`
145+
: '_Note: No similar past events found in the database._'
146+
}`;
103147
};

0 commit comments

Comments
 (0)