From 5f40710e8299cad0707d2a7bc08d46b7c00c65a0 Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Mon, 15 Dec 2025 23:00:05 +0000 Subject: [PATCH] Add new admin dashboard sections and enhance Aegis monitor functionality Introduce new admin routes for sites, logs, and achievements, refactor Aegis monitor to display real data from alerts and auth_logs, and implement JWT token generation for authentication. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 279f1558-c0e3-40e4-8217-be7e9f4c6eca Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 48e7daad-43dd-4684-8441-ff2ea20129e1 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/b984cb14-1d19-4944-922b-bc79e821ed35/279f1558-c0e3-40e4-8217-be7e9f4c6eca/HbU8yEz Replit-Helium-Checkpoint-Created: true --- client/src/App.tsx | 6 + client/src/pages/admin-achievements.tsx | 167 ++++++++++++++++ client/src/pages/admin-aegis.tsx | 244 +++++++++++++----------- client/src/pages/admin-logs.tsx | 166 ++++++++++++++++ client/src/pages/admin-sites.tsx | 192 +++++++++++++++++++ client/src/pages/admin.tsx | 5 +- server/routes.ts | 88 ++++++++- server/storage.ts | 82 +++++++- 8 files changed, 835 insertions(+), 115 deletions(-) create mode 100644 client/src/pages/admin-achievements.tsx create mode 100644 client/src/pages/admin-logs.tsx create mode 100644 client/src/pages/admin-sites.tsx diff --git a/client/src/App.tsx b/client/src/App.tsx index cad758f..7b7fe41 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -16,6 +16,9 @@ import AdminArchitects from "@/pages/admin-architects"; import AdminProjects from "@/pages/admin-projects"; import AdminCredentials from "@/pages/admin-credentials"; import AdminAegis from "@/pages/admin-aegis"; +import AdminSites from "@/pages/admin-sites"; +import AdminLogs from "@/pages/admin-logs"; +import AdminAchievements from "@/pages/admin-achievements"; function Router() { return ( @@ -31,6 +34,9 @@ function Router() { + + + diff --git a/client/src/pages/admin-achievements.tsx b/client/src/pages/admin-achievements.tsx new file mode 100644 index 0000000..e18d485 --- /dev/null +++ b/client/src/pages/admin-achievements.tsx @@ -0,0 +1,167 @@ +import { useEffect } from "react"; +import { motion } from "framer-motion"; +import { Link, useLocation } from "wouter"; +import { useQuery } from "@tanstack/react-query"; +import { useAuth } from "@/lib/auth"; +import { + Users, FileCode, Shield, Activity, LogOut, + BarChart3, User, Globe, Key, Award, Star, Trophy +} from "lucide-react"; + +export default function AdminAchievements() { + const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth(); + const [, setLocation] = useLocation(); + + useEffect(() => { + if (!authLoading && !isAuthenticated) { + setLocation("/login"); + } + }, [authLoading, isAuthenticated, setLocation]); + + const { data: achievements, isLoading } = useQuery({ + queryKey: ["achievements"], + queryFn: async () => { + const res = await fetch("/api/achievements"); + if (!res.ok) throw new Error("Failed to fetch achievements"); + return res.json(); + }, + enabled: isAuthenticated, + }); + + if (authLoading || !isAuthenticated) { + return ( +
+
Loading...
+
+ ); + } + + const handleLogout = async () => { + await logout(); + setLocation("/"); + }; + + const getRarityColor = (rarity: string) => { + switch (rarity?.toLowerCase()) { + case 'legendary': return 'border-yellow-500 bg-yellow-500/10'; + case 'epic': return 'border-purple-500 bg-purple-500/10'; + case 'rare': return 'border-blue-500 bg-blue-500/10'; + case 'uncommon': return 'border-green-500 bg-green-500/10'; + default: return 'border-white/10 bg-white/5'; + } + }; + + return ( +
+ + +
+
+
+

+ Achievements +

+

+ {achievements?.length || 0} achievements configured +

+
+ +
+ {isLoading ? ( +
Loading...
+ ) : achievements?.length === 0 ? ( +
No achievements found
+ ) : ( + achievements?.map((achievement: any) => ( + +
+
+ {achievement.icon || '🏆'} +
+
+

{achievement.name}

+

{achievement.description}

+
+
+ +
+
+
XP
+
+{achievement.xp_reward || 0}
+
+
+
Points
+
+{achievement.points_reward || 0}
+
+
+
Rarity
+
{achievement.rarity || 'common'}
+
+
+ + {achievement.category && ( +
+ + {achievement.category} + +
+ )} +
+ )) + )} +
+
+
+
+ ); +} + +function Sidebar({ user, onLogout, active }: { user: any; onLogout: () => void; active: string }) { + return ( +
+
+

AeThex

+

Command Center

+
+ +
+
+
+ +
+
+
{user?.username}
+
{user?.isAdmin ? "Administrator" : "Member"}
+
+
+ +
+
+ ); +} + +function NavItem({ icon, label, href, active = false }: { icon: React.ReactNode; label: string; href: string; active?: boolean }) { + return ( + +
+ {icon} + {label} +
+ + ); +} diff --git a/client/src/pages/admin-aegis.tsx b/client/src/pages/admin-aegis.tsx index 4aa54b7..285ed04 100644 --- a/client/src/pages/admin-aegis.tsx +++ b/client/src/pages/admin-aegis.tsx @@ -1,9 +1,10 @@ import { useEffect } from "react"; import { Link, useLocation } from "wouter"; +import { useQuery } from "@tanstack/react-query"; import { useAuth } from "@/lib/auth"; import { Users, FileCode, Shield, Activity, LogOut, - BarChart3, User, AlertTriangle, CheckCircle, XCircle, Eye + BarChart3, User, AlertTriangle, CheckCircle, XCircle, Eye, Globe, Award, Key } from "lucide-react"; export default function AdminAegis() { @@ -16,6 +17,26 @@ export default function AdminAegis() { } }, [authLoading, isAuthenticated, setLocation]); + const { data: alerts } = useQuery({ + queryKey: ["alerts"], + queryFn: async () => { + const res = await fetch("/api/alerts"); + if (!res.ok) return []; + return res.json(); + }, + enabled: isAuthenticated, + }); + + const { data: authLogs } = useQuery({ + queryKey: ["auth-logs-recent"], + queryFn: async () => { + const res = await fetch("/api/auth-logs"); + if (!res.ok) return []; + return res.json(); + }, + enabled: isAuthenticated, + }); + if (authLoading || !isAuthenticated) { return (
@@ -29,54 +50,14 @@ export default function AdminAegis() { setLocation("/"); }; - // Mock threat data for demo - const mockThreats = [ - { id: 1, type: "PII Exposure", severity: "high", status: "blocked", timestamp: "2 min ago" }, - { id: 2, type: "Suspicious Pattern", severity: "medium", status: "flagged", timestamp: "15 min ago" }, - { id: 3, type: "Rate Limit", severity: "low", status: "allowed", timestamp: "1 hour ago" }, - ]; + const failedLogins = authLogs?.filter((l: any) => l.event_type?.includes('fail')).length || 0; + const successLogins = authLogs?.filter((l: any) => l.event_type?.includes('success')).length || 0; + const unresolvedAlerts = alerts?.filter((a: any) => !a.is_resolved).length || 0; return (
- {/* Sidebar */} -
-
-

- AeThex -

-

Command Center

-
- - - -
-
-
- -
-
-
{user?.username}
-
- {user?.isAdmin ? "Administrator" : "Member"} -
-
-
- -
-
+ - {/* Main Content */}
@@ -85,7 +66,7 @@ export default function AdminAegis() { Aegis Monitor

- Real-time security monitoring and threat intervention + Real-time security monitoring from your database

@@ -94,78 +75,93 @@ export default function AdminAegis() {
- {/* Stats */}
-
Threats Blocked
-
247
-
Last 24 hours
+
Auth Events
+
{authLogs?.length || 0}
+
Last 100 events
-
PII Scrubbed
-
1,892
-
Instances protected
+
Failed Logins
+
{failedLogins}
+
Blocked attempts
-
Active Sessions
-
34
-
Being monitored
+
Successful Logins
+
{successLogins}
+
Verified access
-
Uptime
-
99.9%
-
Last 30 days
+
Active Alerts
+
{unresolvedAlerts}
+
Unresolved
- {/* Recent Activity */} -
-

- - Recent Threat Activity -

- -
- {mockThreats.map((threat) => ( -
-
-
- + {alerts && alerts.length > 0 && ( +
+

+ + System Alerts +

+
+ {alerts.slice(0, 10).map((alert: any) => ( +
+
+
+ +
+
+
{alert.type}
+
{alert.message}
+
-
-
{threat.type}
-
{threat.timestamp}
+
+ + {alert.is_resolved ? 'resolved' : 'active'} +
-
- - {threat.status} - - + ))} +
+
+ )} + +
+

+ + Recent Auth Events +

+
+ {authLogs?.slice(0, 10).map((log: any) => ( +
+
+ {log.event_type?.includes('success') ? ( + + ) : log.event_type?.includes('fail') ? ( + + ) : ( + + )} + {log.event_type} +
+
+ {log.ip_address || 'N/A'} + {log.created_at ? new Date(log.created_at).toLocaleString() : ''}
))}
- -
-

- Aegis security layer is monitoring all active sessions -

-
@@ -173,19 +169,45 @@ export default function AdminAegis() { ); } -function NavItem({ icon, label, href, active = false }: { - icon: React.ReactNode; - label: string; - href: string; - active?: boolean; -}) { +function Sidebar({ user, onLogout, active }: { user: any; onLogout: () => void; active: string }) { + return ( +
+
+

AeThex

+

Command Center

+
+ +
+
+
+ +
+
+
{user?.username}
+
{user?.isAdmin ? "Administrator" : "Member"}
+
+
+ +
+
+ ); +} + +function NavItem({ icon, label, href, active = false }: { icon: React.ReactNode; label: string; href: string; active?: boolean }) { return ( -
+
{icon} {label}
diff --git a/client/src/pages/admin-logs.tsx b/client/src/pages/admin-logs.tsx new file mode 100644 index 0000000..dea3cf7 --- /dev/null +++ b/client/src/pages/admin-logs.tsx @@ -0,0 +1,166 @@ +import { useEffect } from "react"; +import { motion } from "framer-motion"; +import { Link, useLocation } from "wouter"; +import { useQuery } from "@tanstack/react-query"; +import { useAuth } from "@/lib/auth"; +import { + Users, FileCode, Shield, Activity, LogOut, + BarChart3, User, Globe, Key, CheckCircle, XCircle, AlertTriangle +} from "lucide-react"; + +export default function AdminLogs() { + const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth(); + const [, setLocation] = useLocation(); + + useEffect(() => { + if (!authLoading && !isAuthenticated) { + setLocation("/login"); + } + }, [authLoading, isAuthenticated, setLocation]); + + const { data: logs, isLoading } = useQuery({ + queryKey: ["auth-logs"], + queryFn: async () => { + const res = await fetch("/api/auth-logs"); + if (!res.ok) throw new Error("Failed to fetch logs"); + return res.json(); + }, + enabled: isAuthenticated, + }); + + if (authLoading || !isAuthenticated) { + return ( +
+
Loading...
+
+ ); + } + + const handleLogout = async () => { + await logout(); + setLocation("/"); + }; + + const getEventIcon = (eventType: string) => { + if (eventType?.includes('success') || eventType?.includes('login')) { + return ; + } else if (eventType?.includes('fail') || eventType?.includes('error')) { + return ; + } else if (eventType?.includes('warn')) { + return ; + } + return ; + }; + + return ( +
+ + +
+
+
+

+ Auth Logs +

+

+ {logs?.length || 0} recent authentication events +

+
+ +
+ + + + + + + + + + + + {isLoading ? ( + + + + ) : logs?.length === 0 ? ( + + + + ) : ( + logs?.map((log: any) => ( + + + + + + + + )) + )} + +
EventUserIP AddressUser AgentTime
Loading...
No logs found
+
+ {getEventIcon(log.event_type)} + {log.event_type} +
+
+ {log.user_id?.substring(0, 8)}... + + {log.ip_address || 'N/A'} + + {log.user_agent || 'N/A'} + + {log.created_at ? new Date(log.created_at).toLocaleString() : 'N/A'} +
+
+
+
+
+ ); +} + +function Sidebar({ user, onLogout, active }: { user: any; onLogout: () => void; active: string }) { + return ( +
+
+

AeThex

+

Command Center

+
+ +
+
+
+ +
+
+
{user?.username}
+
{user?.isAdmin ? "Administrator" : "Member"}
+
+
+ +
+
+ ); +} + +function NavItem({ icon, label, href, active = false }: { icon: React.ReactNode; label: string; href: string; active?: boolean }) { + return ( + +
+ {icon} + {label} +
+ + ); +} diff --git a/client/src/pages/admin-sites.tsx b/client/src/pages/admin-sites.tsx new file mode 100644 index 0000000..a11a3a9 --- /dev/null +++ b/client/src/pages/admin-sites.tsx @@ -0,0 +1,192 @@ +import { useEffect } from "react"; +import { motion } from "framer-motion"; +import { Link, useLocation } from "wouter"; +import { useQuery } from "@tanstack/react-query"; +import { useAuth } from "@/lib/auth"; +import { + Users, FileCode, Shield, Activity, LogOut, + BarChart3, User, Globe, CheckCircle, XCircle, AlertTriangle, ExternalLink +} from "lucide-react"; + +export default function AdminSites() { + const { user, isAuthenticated, isLoading: authLoading, logout } = useAuth(); + const [, setLocation] = useLocation(); + + useEffect(() => { + if (!authLoading && !isAuthenticated) { + setLocation("/login"); + } + }, [authLoading, isAuthenticated, setLocation]); + + const { data: sites, isLoading } = useQuery({ + queryKey: ["sites"], + queryFn: async () => { + const res = await fetch("/api/sites"); + if (!res.ok) throw new Error("Failed to fetch sites"); + return res.json(); + }, + enabled: isAuthenticated, + }); + + if (authLoading || !isAuthenticated) { + return ( +
+
Loading...
+
+ ); + } + + const handleLogout = async () => { + await logout(); + setLocation("/"); + }; + + const getStatusColor = (status: string) => { + switch (status?.toLowerCase()) { + case 'online': case 'operational': return 'text-green-500'; + case 'degraded': case 'warning': return 'text-yellow-500'; + case 'offline': case 'down': return 'text-destructive'; + default: return 'text-muted-foreground'; + } + }; + + const getStatusIcon = (status: string) => { + switch (status?.toLowerCase()) { + case 'online': case 'operational': return ; + case 'degraded': case 'warning': return ; + case 'offline': case 'down': return ; + default: return ; + } + }; + + return ( +
+ + +
+
+
+

+ AeThex Sites +

+

+ {sites?.length || 0} monitored sites +

+
+ +
+ {isLoading ? ( +
+ Loading sites... +
+ ) : sites?.length === 0 ? ( +
+ No sites found +
+ ) : ( + sites?.map((site: any) => ( + +
+
+ +

{site.name}

+
+
+ {getStatusIcon(site.status)} + {site.status || 'unknown'} +
+
+ + {site.url && ( + + {site.url} + + )} + +
+
+
Uptime
+
{site.uptime || 0}%
+
+
+
Response
+
{site.response_time || 0}ms
+
+
+
Users
+
{site.users || 0}
+
+
+
Requests
+
{site.requests || 0}
+
+
+ + {site.last_check && ( +
+ Last check: {new Date(site.last_check).toLocaleString()} +
+ )} +
+ )) + )} +
+
+
+
+ ); +} + +function Sidebar({ user, onLogout, active }: { user: any; onLogout: () => void; active: string }) { + return ( +
+
+

AeThex

+

Command Center

+
+ +
+
+
+ +
+
+
{user?.username}
+
{user?.isAdmin ? "Administrator" : "Member"}
+
+
+ +
+
+ ); +} + +function NavItem({ icon, label, href, active = false }: { icon: React.ReactNode; label: string; href: string; active?: boolean }) { + return ( + +
+ {icon} + {label} +
+ + ); +} diff --git a/client/src/pages/admin.tsx b/client/src/pages/admin.tsx index 9cb7169..f9fc33a 100644 --- a/client/src/pages/admin.tsx +++ b/client/src/pages/admin.tsx @@ -5,7 +5,7 @@ import { useQuery } from "@tanstack/react-query"; import { useAuth } from "@/lib/auth"; import { Users, FileCode, Shield, Activity, LogOut, - Home, BarChart3, Settings, ChevronRight, User + Home, BarChart3, Settings, ChevronRight, User, Globe, Award, Key } from "lucide-react"; import gridBg from '@assets/generated_images/dark_subtle_digital_grid_texture.png'; @@ -77,8 +77,11 @@ export default function Admin() { diff --git a/server/routes.ts b/server/routes.ts index a315b2b..fbd40ed 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -3,12 +3,14 @@ import { createServer, type Server } from "http"; import { storage } from "./storage"; import { loginSchema } from "@shared/schema"; import bcrypt from "bcrypt"; +import crypto from "crypto"; // Extend session type declare module 'express-session' { interface SessionData { userId?: string; isAdmin?: boolean; + token?: string; } } @@ -31,6 +33,21 @@ function requireAdmin(req: Request, res: Response, next: NextFunction) { next(); } +// Generate JWT-like token +function generateToken(userId: string, username: string): string { + const header = Buffer.from(JSON.stringify({ alg: "HS256", typ: "JWT" })).toString('base64url'); + const payload = Buffer.from(JSON.stringify({ + userId, + username, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 3600 // 1 hour + })).toString('base64url'); + const signature = crypto.createHmac('sha256', process.env.SESSION_SECRET || 'dev-secret') + .update(`${header}.${payload}`) + .digest('base64url'); + return `${header}.${payload}.${signature}`; +} + export async function registerRoutes( httpServer: Server, app: Express @@ -38,7 +55,7 @@ export async function registerRoutes( // ========== AUTH ROUTES ========== - // Login + // Login - creates JWT token and stores in sessions table app.post("/api/auth/login", async (req, res) => { try { const result = loginSchema.safeParse(req.body); @@ -58,7 +75,19 @@ export async function registerRoutes( return res.status(401).json({ error: "Invalid credentials" }); } - // Regenerate session on login to prevent session fixation + // Generate token like your other apps + const token = generateToken(user.id, user.username); + const expiresAt = new Date(Date.now() + 3600 * 1000); // 1 hour + + // Store session in sessions table (like your other apps) + await storage.createSession({ + user_id: user.id, + username: user.username, + token, + expires_at: expiresAt.toISOString() + }); + + // Also set express session for this app req.session.regenerate((err) => { if (err) { return res.status(500).json({ error: "Session error" }); @@ -66,9 +95,11 @@ export async function registerRoutes( req.session.userId = user.id; req.session.isAdmin = user.is_admin ?? false; + req.session.token = token; res.json({ success: true, + token, user: { id: user.id, username: user.username, @@ -77,6 +108,7 @@ export async function registerRoutes( }); }); } catch (err: any) { + console.error('Login error:', err); res.status(500).json({ error: err.message }); } }); @@ -185,6 +217,58 @@ export async function registerRoutes( res.status(500).json({ error: err.message }); } }); + + // ========== NEW ADMIN ROUTES ========== + + // Get all aethex sites (admin only) + app.get("/api/sites", requireAdmin, async (req, res) => { + try { + const sites = await storage.getSites(); + res.json(sites); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); + + // Get auth logs (admin only) + app.get("/api/auth-logs", requireAdmin, async (req, res) => { + try { + const logs = await storage.getAuthLogs(); + res.json(logs); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); + + // Get achievements (admin only) + app.get("/api/achievements", requireAdmin, async (req, res) => { + try { + const achievements = await storage.getAchievements(); + res.json(achievements); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); + + // Get applications (admin only) + app.get("/api/applications", requireAdmin, async (req, res) => { + try { + const applications = await storage.getApplications(); + res.json(applications); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); + + // Get alerts for Aegis (admin only) + app.get("/api/alerts", requireAdmin, async (req, res) => { + try { + const alerts = await storage.getAlerts(); + res.json(alerts); + } catch (err: any) { + res.status(500).json({ error: err.message }); + } + }); return httpServer; } diff --git a/server/storage.ts b/server/storage.ts index 2c5af7e..b1383c6 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -6,6 +6,9 @@ export interface IStorage { getUser(id: string): Promise; getUserByUsername(username: string): Promise; + // Sessions + createSession(session: { user_id: string; username: string; token: string; expires_at: string }): Promise; + // Profiles getProfiles(): Promise; getProfile(id: string): Promise; @@ -16,6 +19,21 @@ export interface IStorage { getProjects(): Promise; getProject(id: string): Promise; + // Sites + getSites(): Promise; + + // Auth Logs + getAuthLogs(): Promise; + + // Achievements + getAchievements(): Promise; + + // Applications + getApplications(): Promise; + + // Alerts + getAlerts(): Promise; + // Metrics getMetrics(): Promise<{ totalProfiles: number; @@ -51,6 +69,17 @@ export class SupabaseStorage implements IStorage { return data as User; } + async createSession(session: { user_id: string; username: string; token: string; expires_at: string }): Promise { + const { data, error } = await supabase + .from('sessions') + .insert(session) + .select() + .single(); + + if (error) throw error; + return data; + } + async getProfiles(): Promise { const { data, error } = await supabase .from('profiles') @@ -116,6 +145,58 @@ export class SupabaseStorage implements IStorage { return data as Project; } + async getSites(): Promise { + const { data, error } = await supabase + .from('aethex_sites') + .select('*') + .order('last_check', { ascending: false }); + + if (error) return []; + return data || []; + } + + async getAuthLogs(): Promise { + const { data, error } = await supabase + .from('auth_logs') + .select('*') + .order('created_at', { ascending: false }) + .limit(100); + + if (error) return []; + return data || []; + } + + async getAchievements(): Promise { + const { data, error } = await supabase + .from('achievements') + .select('*') + .order('name', { ascending: true }); + + if (error) return []; + return data || []; + } + + async getApplications(): Promise { + const { data, error } = await supabase + .from('applications') + .select('*') + .order('submitted_at', { ascending: false }); + + if (error) return []; + return data || []; + } + + async getAlerts(): Promise { + const { data, error } = await supabase + .from('aethex_alerts') + .select('*') + .order('created_at', { ascending: false }) + .limit(50); + + if (error) return []; + return data || []; + } + async getMetrics(): Promise<{ totalProfiles: number; totalProjects: number; @@ -124,7 +205,6 @@ export class SupabaseStorage implements IStorage { totalXP: number; avgLevel: number; }> { - // Get profiles for metrics const profiles = await this.getProfiles(); const projects = await this.getProjects();