frontend/app/index.tsx (157 lines of code) (raw):

import React, {useEffect, useState} from 'react'; import {render} from 'react-dom'; import {BrowserRouter, Link, Redirect, Route, Switch} from 'react-router-dom'; import Raven from 'raven-js'; import axios from 'axios'; import ScanTargetEdit from './ScanTargets/ScanTargetEdit.jsx'; import ScanTargetsList from './ScanTargets/ScanTargetsList'; import NotFoundComponent from './NotFoundComponent.jsx'; import TopMenu from './TopMenu'; import AdminFront from './admin/AdminFront'; import AboutComponent from './admin/About'; import JobsList from './JobsList/JobsList'; import LoginStatusComponent from './Login/LoginStatusComponent'; import ProxyHealthDetail from './ProxyHealthDetail/ProxyHealthDetail.jsx'; import { library } from '@fortawesome/fontawesome-svg-core' import { faStroopwafel, faCheckCircle, faCheck, faTimes, faTimesCircle, faRoad, faSearch,faThList,faWrench, faLightbulb, faFolderPlus, faFolderMinus, faFolder, faBookReader, faRedoAlt, faHome } from '@fortawesome/free-solid-svg-icons' import { faChevronCircleDown,faChevronCircleRight,faTrashAlt, faFilm, faVolumeUp,faImage, faFile, faClock, faRunning, faExclamationTriangle, faHdd, faBalanceScale, faSyncAlt, faIndustry, faListOl} from '@fortawesome/free-solid-svg-icons' import { faCompressArrowsAlt, faBug, faExclamation, faUnlink } from '@fortawesome/free-solid-svg-icons' import UserList from "./Users/UserList.jsx"; import ProxyFrameworkList from "./ProxyFramework/ProxyFrameworkList"; import ProxyFrameworkAdd from './ProxyFramework/ProxyFrameworkAdd'; import Test419Component from "./testing/test419.jsx"; import { createMuiTheme, ThemeProvider } from "@material-ui/core/styles"; import CssBaseline from "@material-ui/core/CssBaseline"; import {customisedTheme} from "./CustomisedTheme"; import {CircularProgress, Theme} from "@material-ui/core"; import NewBasicSearch from "./search/NewBasicSearch"; import NewBrowseComponent from "./browse/NewBrowseComponent"; import NewLightbox from "./Lightbox/NewLightbox"; import QuickRestoreComponent from "./admin/QuickRestore.jsx"; import PathCacheAdmin from "./admin/PathCacheAdmin.jsx"; import ItemView from "./ItemView/ItemView"; import DeletedItemsComponent from "./DeletedItems/DeletedItems"; import {UserContextProvider} from "./Context/UserContext"; import {GenericResponse, InvalidLoginResponse, UserDetails} from "./types"; import LoginComponent from "./LoginComponent"; import ManualImporter from "./admin/ManualImporter"; library.add(faStroopwafel, faCheckCircle, faCheck, faTimes, faTimesCircle, faRoad,faSearch,faThList,faWrench, faLightbulb, faChevronCircleDown, faChevronCircleRight, faTrashAlt, faFolderPlus, faFolderMinus, faFolder); library.add(faFilm, faVolumeUp, faImage, faFile, faClock, faRunning, faExclamationTriangle, faHdd, faBalanceScale, faSyncAlt, faBookReader, faBug, faCompressArrowsAlt, faIndustry, faRedoAlt, faHome, faListOl,); library.add(faExclamation, faUnlink); const maxLoginAttempts = 5; const App:React.FC = ()=> { const [userLogin, setUserLogin] = useState<UserDetails|undefined>(undefined); const [loading, setLoading] = useState(true); const [lastError, setLastError] = useState<string|undefined>(undefined); const theme = createMuiTheme(customisedTheme); const asyncSleep = (sleeptime:number) => { return new Promise<void>((resolve, reject)=>window.setTimeout(()=>resolve(), sleeptime)); } const checkLoginRefresh = async (attempt:number):Promise<void> => { if(attempt>maxLoginAttempts) throw `Could not refresh login after ${attempt} attempts, giving up`; if(attempt>1) await asyncSleep(1000*(attempt-1)); //if we are in a retry loop don't spam the server try { const refreshResponse = await axios.post<GenericResponse>("/api/loginRefresh"); if(refreshResponse.data.status=="not_needed") { console.log("token refresh not required"); setLoading(false); } else { console.log(`refresh check successful on attempt ${attempt}: `, refreshResponse.data); return getLoginStatus(attempt + 1); } } catch(err) { console.error("could not refresh login: ") setLoading(false); } } const getLoginStatus = async (attempt:number) => { try { const loginStatusResponse = await axios.get("/api/loginStatus", {validateStatus: (status) => status == 200 || status == 401}) switch (loginStatusResponse.status) { case 200: const userLoginData = loginStatusResponse.data as UserDetails; setUserLogin(userLoginData); setLoading(false); break; case 401 || 403: //we get this if the login is expired const rejectionData = loginStatusResponse.data as InvalidLoginResponse; if (rejectionData.status == "expired") { return checkLoginRefresh(attempt); } else { setLastError(rejectionData.detail ?? rejectionData.toString()); setLoading(false); } break; default: console.error("Unexpected response: ", loginStatusResponse.status, " ", loginStatusResponse.data); setLastError(`Unexpected server response ${loginStatusResponse.status}`); setLoading(false); break; } } catch(err) { console.error(`Could not check login status: `, err); setLastError(err.toString()); setLoading(false); } } useEffect(()=>{ setLoading(true); axios.get("/system/publicdsn").then(response=> { Raven .config(response.data.publicDsn) .install(); console.log("Sentry initialised for " + response.data.publicDsn); }).catch(error => { console.error("Could not intialise sentry", error); }); getLoginStatus(1) .catch((err)=>{ console.error("Could not refresh login: ", err); }) const timerId = window.setInterval(()=>checkLoginRefresh(1), 60000) //check for token refresh once per minute return ()=>{ window.clearInterval(timerId); } }, []); const userLoggedOut = () => { setUserLogin(undefined); asyncSleep(500).then(()=>window.location.reload()); } return <ThemeProvider theme={theme}> <UserContextProvider value={{profile: userLogin, updateProfile: setUserLogin}}> <CssBaseline/> <TopMenu visible={true} isAdmin={true}/> <LoginStatusComponent userLoggedOutCb={userLoggedOut}/> {loading ? <CircularProgress/> : userLogin ? <Switch> <Route path="/test/419" component={Test419Component}/> <Route path="/admin/pathcache" component={PathCacheAdmin}/> <Route path="/admin/proxyHealth" component={ProxyHealthDetail}/> <Route path="/admin/deleteditems" component={DeletedItemsComponent}/> <Route path="/admin/proxyFramework/new" component={ProxyFrameworkAdd}/> <Route path="/admin/proxyFramework" component={ProxyFrameworkList}/> <Route path="/admin/about" component={AboutComponent}/> <Route path="/admin/users" component={UserList}/> <Route path="/admin/jobs/:jobid" component={JobsList}/> <Route path="/admin/jobs" component={JobsList}/> <Route path="/admin/scanTargets/:id" component={ScanTargetEdit}/> <Route path="/admin/scanTargets" component={ScanTargetsList}/> <Route path="/admin/quickrestore" component={QuickRestoreComponent}/> <Route path="/admin/manualimport" component={ManualImporter}/> <Route path="/admin" exact={true} component={AdminFront}/> <Route path="/lightbox" exact={true} component={NewLightbox}/> <Route path="/browse" exact={true} component={NewBrowseComponent}/> <Route path="/item/:id" exact={true} component={ItemView}/> <Route path="/search" exact={true} component={NewBasicSearch}/> <Route path="/" exact={true} render={() => <Redirect to="/search"/>}/> <Route default component={NotFoundComponent}/> </Switch> : <LoginComponent/> } </UserContextProvider> </ThemeProvider> } render(<BrowserRouter basename="/"><App/></BrowserRouter>, document.getElementById('app'));