Skip to content

Commit e3c5eae

Browse files
committed
Merge branch 'dev' into readme
2 parents 3781afb + 8b19400 commit e3c5eae

23 files changed

+485
-156
lines changed

.dockerignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
**/.classpath
88
**/.dockerignore
9-
# **/.env
9+
**/.env
1010
**/.git
1111
**/.gitignore
1212
**/.project

client/src/App.tsx

Lines changed: 92 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState } from 'react';
1+
import React, { ReactNode, useCallback, useEffect, useState } from 'react';
22
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
33

44
import Navbar from './components/Navbar';
@@ -7,30 +7,114 @@ import Home from './pages/Home';
77
import EventsDashboard from './pages/EventsDashboard';
88
import Login from './pages/Login';
99
import SignUp from './pages/SignUp';
10+
import { AWSCredentials, UserDetails } from './types';
1011

1112
const App: React.FC = () => {
1213
const [isDarkMode, setIsDarkMode] = useState(false); // Dark mode state
13-
const [user, setUser] = useState<Record<string, string> | null>(null);
14+
const [user, setUser] = useState<UserDetails | null>(null);
15+
16+
const updateCredentials = useCallback(
17+
function (credentials: AWSCredentials): void {
18+
const locallyStoredUser: UserDetails = JSON.parse(
19+
window.localStorage.getItem('user')!
20+
) as UserDetails;
21+
fetch('/credentials', {
22+
method: 'POST',
23+
body: JSON.stringify({
24+
...credentials,
25+
username:
26+
user?.username ?? locallyStoredUser.username ?? 'No Active User',
27+
}),
28+
headers: {
29+
'Content-Type': 'application/json',
30+
},
31+
})
32+
.then((response) => {
33+
if (!response.ok)
34+
throw Error('Server Error while updating aws credentials');
35+
return response.json();
36+
})
37+
.then((data: UserDetails) => {
38+
setUser(data);
39+
window.localStorage.setItem('user', JSON.stringify(data));
40+
})
41+
.catch((error: Error) => {
42+
console.error(error);
43+
});
44+
},
45+
// we don't want to update on user update, because it would create an infinte loop, only on app reload
46+
// eslint-disable-next-line react-hooks/exhaustive-deps
47+
[]
48+
);
49+
50+
// check for a user session and update the user if found
51+
useEffect(() => {
52+
if (window.localStorage.getItem('user')) {
53+
const locallyStoredUser: UserDetails = JSON.parse(
54+
window.localStorage.getItem('user')!
55+
) as UserDetails;
56+
setUser(locallyStoredUser);
57+
}
58+
}, []);
1459

1560
const toggleDarkMode = () => {
1661
setIsDarkMode((prev) => !prev);
1762
document.body.classList.toggle('dark-mode', !isDarkMode); // Toggle class based on state
1863
};
1964

65+
function checkLogin(component: ReactNode): ReactNode {
66+
return user ? component : <p>You must login to see this page</p>;
67+
}
68+
69+
function checkAWSCreds(component: ReactNode): ReactNode {
70+
if (
71+
user?.aws_access_key?.length &&
72+
user?.aws_region?.length > 0 &&
73+
user?.aws_secret_access_key?.length > 0
74+
) {
75+
return component;
76+
}
77+
return (
78+
<p>
79+
You must enter your AWS credentials in the profile page to see any data
80+
here.
81+
</p>
82+
);
83+
}
84+
2085
return (
2186
<Router>
22-
<Navbar toggleDarkMode={toggleDarkMode} isDarkMode={isDarkMode} username={user?.username ?? null} setUser={setUser} />
87+
<Navbar
88+
toggleDarkMode={toggleDarkMode}
89+
isDarkMode={isDarkMode}
90+
username={user?.username ?? null}
91+
setUser={setUser}
92+
/>
2393
<Routes>
94+
<Route path="/" element={<Login setUser={setUser} />} />
2495
<Route path="/login" element={<Login setUser={setUser} />} />
2596
<Route path="/signup" element={<SignUp />} />
26-
{/* {user !== null && <> */}
27-
<Route path="/" element={<Home isDarkMode={isDarkMode} />} />
28-
<Route path="/profile" element={<Profile isDarkMode={isDarkMode} user={user} />} />
97+
98+
<Route
99+
path="/home"
100+
element={checkLogin(checkAWSCreds(<Home isDarkMode={isDarkMode} />))}
101+
/>
102+
<Route
103+
path="/profile"
104+
element={checkLogin(
105+
<Profile
106+
isDarkMode={isDarkMode}
107+
user={user}
108+
updateCredentials={updateCredentials}
109+
/>
110+
)}
111+
/>
29112
<Route
30113
path="/events-dashboard"
31-
element={<EventsDashboard isDarkMode={isDarkMode} />}
114+
element={checkLogin(
115+
checkAWSCreds(<EventsDashboard isDarkMode={isDarkMode} />)
116+
)}
32117
/>
33-
{/* </>} */}
34118
</Routes>
35119
</Router>
36120
);

client/src/components/IpAccessCombined.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ export default function IpAccessCombined({
1313

1414
useEffect(() => {
1515
fetch('/events?countOn=source_ip&includeLocation=true')
16-
.then((response) => response.json())
17-
.then((data: (IPLocation & CountedEvent)[] | { err: string }) => {
18-
if (!Object.prototype.hasOwnProperty.call(Object, 'err'))
19-
setIpLocCounts(() => data as (IPLocation & CountedEvent)[]);
16+
.then((response) => {
17+
if (response.ok) return response.json();
18+
throw new Error(response.status + ': ' + response.statusText);
2019
})
20+
.then((data: (IPLocation & CountedEvent)[] | { err: string }) =>
21+
setIpLocCounts(() => data as (IPLocation & CountedEvent)[])
22+
)
2123
.catch((error) =>
2224
console.warn('IpAccessCombined: fetch error: ' + error)
2325
);

client/src/components/Navbar.tsx

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ import { NavbarProps } from '../types';
44
import LOGO from '../assets/RAILGUIDE.png';
55
//import '../index.scss';
66

7-
const Navbar: React.FC<NavbarProps> = ({ toggleDarkMode, isDarkMode, username, setUser }) => {
7+
const Navbar: React.FC<NavbarProps> = ({
8+
toggleDarkMode,
9+
isDarkMode,
10+
username,
11+
setUser,
12+
}) => {
813
const [dropdownOpen, setDropdownOpen] = useState(false);
914
const dropdownRef = useRef<HTMLDivElement>(null);
1015
const navigate = useNavigate();
@@ -14,14 +19,17 @@ const Navbar: React.FC<NavbarProps> = ({ toggleDarkMode, isDarkMode, username, s
1419
};
1520

1621
const handleLogout = () => {
17-
console.log('User logged out');
1822
setUser(null);
23+
window.localStorage.removeItem('user');
1924
navigate('/login');
2025
};
2126

2227
useEffect(() => {
2328
const handleClickOutside = (event: MouseEvent) => {
24-
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
29+
if (
30+
dropdownRef.current &&
31+
!dropdownRef.current.contains(event.target as Node)
32+
) {
2533
setDropdownOpen(false);
2634
}
2735
};
@@ -34,26 +42,27 @@ const Navbar: React.FC<NavbarProps> = ({ toggleDarkMode, isDarkMode, username, s
3442

3543
return (
3644
<nav className={isDarkMode ? 'dark-mode' : ''}>
37-
<Link to="/" className="logo" title="Home">
45+
<Link to="/home" className="logo" title="Home">
3846
<img src={LOGO} alt="Wood Plank T" className="logo-image" />
3947
</Link>
4048
<div className="nav-buttons">
4149
<Link to="/events-dashboard" className="nav-button">
42-
EVENTS DASHBOARD
50+
RECENT EVENTS
4351
</Link>
4452
<button onClick={toggleDarkMode} className="nav-button">
4553
{isDarkMode ? 'LIGHT MODE' : 'DARK MODE'}
4654
</button>
47-
48-
<div
49-
className="nav-button"
50-
onClick={toggleDropdown}
51-
aria-haspopup="true"
52-
aria-expanded={dropdownOpen}
53-
>
54-
{username && typeof username === 'string' ? username.toUpperCase() : "USER"}
55-
</div>
56-
55+
56+
<div
57+
className="nav-button"
58+
onClick={toggleDropdown}
59+
aria-haspopup="true"
60+
aria-expanded={dropdownOpen}
61+
>
62+
{username && typeof username === 'string'
63+
? username.toUpperCase()
64+
: 'USER'}
65+
</div>
5766
</div>
5867
{dropdownOpen && (
5968
<div className="dropdown" ref={dropdownRef}>

client/src/components/charts/AnomalyChart.tsx

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
Tooltip,
99
ResponsiveContainer,
1010
Legend,
11-
ScatterProps,
1211
} from 'recharts';
1312

1413
interface DataPoint {
@@ -47,15 +46,19 @@ const AnomalyChart: React.FC = () => {
4746
fill="#8884d8"
4847
shape="circle"
4948
>
50-
{dummyData.map((entry, index) => (
51-
<circle
52-
key={`dot-${index}`}
53-
cx={entry.cx}
54-
cy={entry.cy}
55-
r={isAnomaly(entry.count) ? 8 : 4}
56-
fill={isAnomaly(entry.count) ? '#FF0000' : '#0088FE'}
57-
/>
58-
))}
49+
{dummyData.map((entry, index) => {
50+
const x = new Date(entry.timestamp).getTime();
51+
const y = entry.count;
52+
return (
53+
<circle
54+
key={`dot-${index}`}
55+
cx={x}
56+
cy={y}
57+
r={isAnomaly(entry.count) ? 8 : 4}
58+
fill={isAnomaly(entry.count) ? '#FF0000' : '#0088FE'}
59+
/>
60+
);
61+
})}
5962
</Scatter>
6063
</ScatterChart>
6164
</ResponsiveContainer>

client/src/components/charts/EventSource.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ export default function EventSourceChart() {
2222
useEffect(() => {
2323
setLoading(true);
2424
fetch('/events?countOn=source')
25-
.then((response) => response.json())
25+
.then((response) => {
26+
if (response.ok) return response.json();
27+
throw new Error(response.status + ': ' + response.statusText);
28+
})
2629
.then((data: CountedEvent[] | { err: string }) => {
27-
if (!Object.prototype.hasOwnProperty.call(Object, 'err'))
28-
setEvents(data as CountedEvent[]);
30+
setEvents(data as CountedEvent[]);
2931
setLoading(false);
3032
})
3133
.catch((error) =>

client/src/components/charts/EventType.tsx

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,22 @@ export default function EventTypeChart() {
2121

2222
useEffect(() => {
2323
setLoading(true);
24-
fetch('/events?countOn=type')
25-
.then((response) => response.json())
24+
fetch('/events?countOn=name')
25+
.then((response) => {
26+
if (response.ok) return response.json();
27+
throw new Error(response.status + ': ' + response.statusText);
28+
})
2629
.then((data: CountedEvent[] | { err: string }) => {
27-
if (!Object.prototype.hasOwnProperty.call(Object, 'err'))
28-
setEvents(
29-
(data as CountedEvent[]).map((event) => ({
30-
...event,
31-
type: event.type.replace(/([A-Z])/g, ' $1'),
32-
}))
33-
);
30+
setEvents(
31+
(data as CountedEvent[]).map((event) => ({
32+
...event,
33+
name: event.name.replace(/([A-Z])/g, ' $1'),
34+
}))
35+
);
3436
setLoading(false);
3537
})
3638
.catch((error) =>
37-
console.warn('Could not fetch event type counts: ', error)
39+
console.warn('Could not fetch event name counts: ', error)
3840
);
3941
}, []);
4042

@@ -64,7 +66,12 @@ export default function EventTypeChart() {
6466
angle={-30}
6567
textAnchor="end"
6668
/>
67-
<Bar dataKey="count" maxBarSize={35} minPointSize={5} style={{ cursor: 'pointer' }}>
69+
<Bar
70+
dataKey="count"
71+
maxBarSize={35}
72+
minPointSize={5}
73+
style={{ cursor: 'pointer' }}
74+
>
6875
{events.map((data, index) => (
6976
<Cell
7077
key={`cell-${index}`}

client/src/components/charts/HeatMap.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@ const HeatMap: React.FC = () => {
2525
.catch((error) => console.error('Error fetching geoJSON:', error));
2626

2727
fetch('/events?countOn=source_ip&includeLocation=true')
28-
.then((response) => response.json())
29-
.then((data: (IPLocation & CountedEvent)[] | { err: string }) => {
30-
if (!Object.prototype.hasOwnProperty.call(Object, 'err'))
31-
setIpData(() => data as (IPLocation & CountedEvent)[]);
28+
.then((response) => {
29+
if (response.ok) return response.json();
30+
throw new Error(response.status + ': ' + response.statusText);
3231
})
32+
.then((data: (IPLocation & CountedEvent)[] | { err: string }) =>
33+
setIpData(() => data as (IPLocation & CountedEvent)[])
34+
)
3335
.catch((error) =>
3436
console.warn('Could not fetch event ip counts and locations: ', error)
3537
);

client/src/components/charts/IpAccessOverTime.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ export default function IpAccessOverTimeChart({
1313
useEffect(() => {
1414
setLoading(true); // Set loading to true before fetching data
1515
fetch('/events?countOn=time&groupTimeBy=minute')
16-
.then((response) => response.json())
16+
.then((response) => {
17+
if (response.ok) return response.json();
18+
throw new Error(response.status + ': ' + response.statusText);
19+
})
1720
.then((data: CountedEvent[] | { err: string }) => {
18-
if (!Object.prototype.hasOwnProperty.call(Object, 'err'))
19-
setIpTimes(() => data as CountedEvent[]);
21+
setIpTimes(() => data as CountedEvent[]);
2022
setLoading(false); // Set loading to true before fetching data
2123
})
2224
.catch((error) =>

0 commit comments

Comments
 (0)