diff --git a/decnet_web/src/App.tsx b/decnet_web/src/App.tsx index c65601b..e816a3c 100644 --- a/decnet_web/src/App.tsx +++ b/decnet_web/src/App.tsx @@ -1,7 +1,11 @@ import { useState, useEffect } from 'react'; +import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import Login from './components/Login'; import Layout from './components/Layout'; import Dashboard from './components/Dashboard'; +import LiveLogs from './components/LiveLogs'; +import Attackers from './components/Attackers'; +import Config from './components/Config'; function App() { const [token, setToken] = useState(localStorage.getItem('token')); @@ -32,9 +36,17 @@ function App() { } return ( - - - + + + + } /> + } /> + } /> + } /> + } /> + + + ); } diff --git a/decnet_web/src/components/Attackers.tsx b/decnet_web/src/components/Attackers.tsx new file mode 100644 index 0000000..0ed1ce9 --- /dev/null +++ b/decnet_web/src/components/Attackers.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Activity } from 'lucide-react'; +import './Dashboard.css'; + +const Attackers: React.FC = () => { + return ( +
+
+ +

ATTACKER PROFILES

+
+
+

NO ACTIVE THREATS PROFILED YET.

+

(Attackers view placeholder)

+
+
+ ); +}; + +export default Attackers; diff --git a/decnet_web/src/components/Config.tsx b/decnet_web/src/components/Config.tsx new file mode 100644 index 0000000..5c41911 --- /dev/null +++ b/decnet_web/src/components/Config.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Settings } from 'lucide-react'; +import './Dashboard.css'; + +const Config: React.FC = () => { + return ( +
+
+ +

SYSTEM CONFIGURATION

+
+
+

CONFIGURATION READ-ONLY MODE ACTIVE.

+

(Config view placeholder)

+
+
+ ); +}; + +export default Config; diff --git a/decnet_web/src/components/Layout.tsx b/decnet_web/src/components/Layout.tsx index 1626183..1a76d53 100644 --- a/decnet_web/src/components/Layout.tsx +++ b/decnet_web/src/components/Layout.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { NavLink } from 'react-router-dom'; import { Menu, X, Search, Activity, LayoutDashboard, Terminal, Settings, LogOut } from 'lucide-react'; import './Layout.css'; @@ -30,10 +31,10 @@ const Layout: React.FC = ({ children, onLogout, onSearch }) => {
@@ -72,17 +73,17 @@ const Layout: React.FC = ({ children, onLogout, onSearch }) => { }; interface NavItemProps { + to: string; icon: React.ReactNode; label: string; - active?: boolean; open: boolean; } -const NavItem: React.FC = ({ icon, label, active, open }) => ( -
+const NavItem: React.FC = ({ to, icon, label, open }) => ( + `nav-item ${isActive ? 'active' : ''}`} end={to === '/'}> {icon} {open && {label}} -
+ ); export default Layout; diff --git a/decnet_web/src/components/LiveLogs.tsx b/decnet_web/src/components/LiveLogs.tsx new file mode 100644 index 0000000..0df96f5 --- /dev/null +++ b/decnet_web/src/components/LiveLogs.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Terminal } from 'lucide-react'; +import './Dashboard.css'; + +const LiveLogs: React.FC = () => { + return ( +
+
+ +

FULL LIVE LOG STREAM

+
+
+

STREAM ESTABLISHED. WAITING FOR INCOMING DATA...

+

(Dedicated Live Logs view placeholder)

+
+
+ ); +}; + +export default LiveLogs; diff --git a/decnet_web/src/components/Login.tsx b/decnet_web/src/components/Login.tsx index 0d93296..aac1d06 100644 --- a/decnet_web/src/components/Login.tsx +++ b/decnet_web/src/components/Login.tsx @@ -12,19 +12,58 @@ const Login: React.FC = ({ onLogin }) => { const [password, setPassword] = useState(''); const [error, setError] = useState(''); const [loading, setLoading] = useState(false); + const [needsPasswordChange, setNeedsPasswordChange] = useState(false); + const [newPassword, setNewPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [tempToken, setTempToken] = useState(''); - const handleSubmit = async (e: React.FormEvent) => { + const handleLoginSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setError(''); try { const response = await api.post('/auth/login', { username, password }); + const { access_token, must_change_password } = response.data; + + if (must_change_password) { + setTempToken(access_token); + setNeedsPasswordChange(true); + } else { + localStorage.setItem('token', access_token); + onLogin(access_token); + } + } catch (err: any) { + setError(err.response?.data?.detail || 'Authentication failed'); + } finally { + setLoading(false); + } + }; + + const handleChangePasswordSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (newPassword !== confirmPassword) { + setError('Passwords do not match'); + return; + } + + setLoading(true); + setError(''); + + try { + await api.post('/auth/change-password', + { old_password: password, new_password: newPassword }, + { headers: { Authorization: `Bearer ${tempToken}` } } + ); + + // Re-authenticate to get a fresh token with must_change_password=false + const response = await api.post('/auth/login', { username, password: newPassword }); const { access_token } = response.data; + localStorage.setItem('token', access_token); onLogin(access_token); } catch (err: any) { - setError(err.response?.data?.detail || 'Authentication failed'); + setError(err.response?.data?.detail || 'Password change failed'); } finally { setLoading(false); } @@ -39,33 +78,70 @@ const Login: React.FC = ({ onLogin }) => {

AUTHORIZED PERSONNEL ONLY

-
-
- - setUsername(e.target.value)} - required - /> -
- -
- - setPassword(e.target.value)} - required - /> -
+ {!needsPasswordChange ? ( + +
+ + setUsername(e.target.value)} + required + /> +
+ +
+ + setPassword(e.target.value)} + required + /> +
- {error &&
{error}
} + {error &&
{error}
} - -
+ + + ) : ( +
+
+

MANDATORY SECURITY UPDATE

+

Please establish a new access key

+
+ +
+ + setNewPassword(e.target.value)} + required + minLength={8} + /> +
+ +
+ + setConfirmPassword(e.target.value)} + required + minLength={8} + /> +
+ + {error &&
{error}
} + + +
+ )}
SECURE PROTOCOL v1.0