tanyar09 d0dd5c82ea
Some checks failed
CI / skip-ci-check (pull_request) Successful in 8s
CI / lint-and-type-check (pull_request) Successful in 54s
CI / test-backend (pull_request) Has been cancelled
CI / build (pull_request) Has been cancelled
CI / secret-scanning (pull_request) Has been cancelled
CI / dependency-scan (pull_request) Has been cancelled
CI / sast-scan (pull_request) Has been cancelled
CI / workflow-summary (pull_request) Has been cancelled
CI / python-lint (pull_request) Has been cancelled
feat: add click logging for admin frontend
- Add backend click logging utility with file rotation and retention
- Add POST /api/v1/log/click endpoint for logging click events
- Add frontend click logger service with batching and context extraction
- Add global click handler in App.tsx for authenticated users
- Add log cleanup script for old log files
- Update QUICK_LOG_REFERENCE.md with click log documentation

Logs are written to /opt/punimtag/logs/admin-clicks.log with:
- Auto-rotation at 10MB (keeps 5 backups)
- 30-day retention
- Format: timestamp | username | page | element_type | element_id | element_text | context
2026-02-05 17:50:15 +00:00

175 lines
5.0 KiB
TypeScript

import { useState, useEffect } from 'react'
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import { AuthProvider, useAuth } from './context/AuthContext'
import { DeveloperModeProvider } from './context/DeveloperModeContext'
import Login from './pages/Login'
import Dashboard from './pages/Dashboard'
import Search from './pages/Search'
import Scan from './pages/Scan'
import Process from './pages/Process'
import Identify from './pages/Identify'
import AutoMatch from './pages/AutoMatch'
import Modify from './pages/Modify'
import Tags from './pages/Tags'
import FacesMaintenance from './pages/FacesMaintenance'
import ApproveIdentified from './pages/ApproveIdentified'
import ManageUsers from './pages/ManageUsers'
import ReportedPhotos from './pages/ReportedPhotos'
import PendingPhotos from './pages/PendingPhotos'
import UserTaggedPhotos from './pages/UserTaggedPhotos'
import ManagePhotos from './pages/ManagePhotos'
import Settings from './pages/Settings'
import Help from './pages/Help'
import Layout from './components/Layout'
import PasswordChangeModal from './components/PasswordChangeModal'
import AdminRoute from './components/AdminRoute'
import { logClick, flushPendingClicks } from './services/clickLogger'
function PrivateRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading, passwordChangeRequired } = useAuth()
const [showPasswordModal, setShowPasswordModal] = useState(false)
useEffect(() => {
if (isAuthenticated && passwordChangeRequired) {
setShowPasswordModal(true)
}
}, [isAuthenticated, passwordChangeRequired])
if (isLoading) {
return <div className="min-h-screen flex items-center justify-center">Loading...</div>
}
if (!isAuthenticated) {
return <Navigate to="/login" replace />
}
return (
<>
{showPasswordModal && (
<PasswordChangeModal
onSuccess={() => {
setShowPasswordModal(false)
}}
/>
)}
{children}
</>
)
}
function AppRoutes() {
const { isAuthenticated } = useAuth()
// Set up global click logging for authenticated users
useEffect(() => {
if (!isAuthenticated) {
return
}
const handleClick = (event: MouseEvent) => {
const target = event.target as HTMLElement
if (target) {
logClick(target)
}
}
// Add click listener
document.addEventListener('click', handleClick, true) // Use capture phase
// Flush pending clicks on page unload
const handleBeforeUnload = () => {
flushPendingClicks()
}
window.addEventListener('beforeunload', handleBeforeUnload)
return () => {
document.removeEventListener('click', handleClick, true)
window.removeEventListener('beforeunload', handleBeforeUnload)
// Flush any pending clicks on cleanup
flushPendingClicks()
}
}, [isAuthenticated])
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/"
element={
<PrivateRoute>
<Layout />
</PrivateRoute>
}
>
<Route index element={<Dashboard />} />
<Route path="scan" element={<Scan />} />
<Route path="process" element={<Process />} />
<Route path="search" element={<Search />} />
<Route path="identify" element={<Identify />} />
<Route path="auto-match" element={<AutoMatch />} />
<Route path="modify" element={<Modify />} />
<Route path="tags" element={<Tags />} />
<Route path="manage-photos" element={<ManagePhotos />} />
<Route path="faces-maintenance" element={<FacesMaintenance />} />
<Route
path="approve-identified"
element={
<AdminRoute featureKey="user_identified">
<ApproveIdentified />
</AdminRoute>
}
/>
<Route
path="manage-users"
element={
<AdminRoute featureKey="manage_users">
<ManageUsers />
</AdminRoute>
}
/>
<Route
path="reported-photos"
element={
<AdminRoute featureKey="user_reported">
<ReportedPhotos />
</AdminRoute>
}
/>
<Route
path="pending-linkages"
element={
<AdminRoute featureKey="user_tagged">
<UserTaggedPhotos />
</AdminRoute>
}
/>
<Route
path="pending-photos"
element={
<AdminRoute featureKey="user_uploaded">
<PendingPhotos />
</AdminRoute>
}
/>
<Route path="settings" element={<Settings />} />
<Route path="help" element={<Help />} />
</Route>
</Routes>
)
}
function App() {
return (
<AuthProvider>
<DeveloperModeProvider>
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
</DeveloperModeProvider>
</AuthProvider>
)
}
export default App