import Head from 'next/head'; import { NextRouter, useRouter } from 'next/router'; import { FC, useEffect, useRef, useState } from 'react'; import { Box, Divider } from '@mui/material'; import { Add as AddIcon } from '@mui/icons-material'; import API_BASE_URL from '../lib/consts/API_BASE_URL'; import { DIVIDER } from '../lib/consts/DEFAULT_THEME'; import { Preview } from '../components/Display'; import fetchJSON from '../lib/fetchers/fetchJSON'; import Header from '../components/Header'; import IconButton from '../components/IconButton'; import Link from '../components/Link'; import OutlinedInput from '../components/OutlinedInput'; import { Panel, PanelHeader } from '../components/Panels'; import periodicFetch from '../lib/fetchers/periodicFetch'; import ProvisionServerDialog from '../components/ProvisionServerDialog'; import Spinner from '../components/Spinner'; import { last } from '../lib/time'; type ServerListItem = ServerOverviewMetadata & { isScreenshotStale?: boolean; loading?: boolean; screenshot: string; timestamp: number; }; const createServerPreviewContainer = ( servers: ServerListItem[], router: NextRouter, ) => ( *': { width: { xs: '20em', md: '24em' }, }, '& > :not(:last-child)': { marginRight: '2em', }, }} > {servers.map( ({ anvilName, anvilUUID, isScreenshotStale, loading, screenshot, serverName, serverState, serverUUID, timestamp, }) => ( {serverName} , {anvilName} , ]} isExternalLoading={loading} isExternalPreviewStale={isScreenshotStale} isFetchPreview={false} isShowControls={false} isUseInnerPanel key={`server-preview-${serverUUID}`} onClickPreview={() => { router.push( `/server?uuid=${serverUUID}&server_name=${serverName}&server_state=${serverState}&vnc=1`, ); }} serverState={serverState} serverUUID={serverUUID} /> ), )} ); const filterServers = (allServers: ServerListItem[], searchTerm: string) => searchTerm === '' ? { exclude: allServers, include: [], } : allServers.reduce<{ exclude: ServerListItem[]; include: ServerListItem[]; }>( (reduceContainer, server) => { const { serverName } = server; if (serverName.includes(searchTerm)) { reduceContainer.include.push(server); } else { reduceContainer.exclude.push(server); } return reduceContainer; }, { exclude: [], include: [] }, ); const Dashboard: FC = () => { const componentMountedRef = useRef(true); const router = useRouter(); const [allServers, setAllServers] = useState([]); const [excludeServers, setExcludeServers] = useState([]); const [includeServers, setIncludeServers] = useState([]); const [inputSearchTerm, setInputSearchTerm] = useState(''); const [isOpenProvisionServerDialog, setIsOpenProvisionServerDialog] = useState(false); const updateServerList = ( ...filterArgs: Parameters ) => { const { exclude, include } = filterServers(...filterArgs); if (!componentMountedRef.current) { return; } setExcludeServers(exclude); setIncludeServers(include); }; const { isLoading } = periodicFetch( `${API_BASE_URL}/server`, { onSuccess: (data = []) => { const serverListItems: ServerListItem[] = ( data as ServerOverviewMetadata[] ).map((serverOverview) => { const { serverUUID } = serverOverview; const previousScreenshot: string = allServers.find(({ serverUUID: uuid }) => uuid === serverUUID) ?.screenshot || ''; const item: ServerListItem = { ...serverOverview, loading: true, screenshot: previousScreenshot, timestamp: 0, }; fetchJSON<{ screenshot: string; timestamp: number }>( `${API_BASE_URL}/server/${serverUUID}?ss=1`, ) .then(({ screenshot, timestamp }) => { if (screenshot.length === 0) return; item.isScreenshotStale = !last(timestamp, 300); item.loading = false; item.screenshot = screenshot; item.timestamp = timestamp; const allServersWithScreenshots = [...serverListItems]; if (!componentMountedRef.current) { return; } setAllServers(allServersWithScreenshots); // Don't update servers to include or exclude here to avoid // updating using an outdated input search term. Remember this // block is async and takes a lot longer to complete compared to // the overview fetch. }) .catch(() => { item.isScreenshotStale = true; }) .finally(() => { item.loading = false; }); return item; }); setAllServers(serverListItems); updateServerList(serverListItems, inputSearchTerm); }, refreshInterval: 60000, }, ); useEffect( () => () => { componentMountedRef.current = false; }, [], ); return ( Dashboard
{isLoading ? ( ) : ( <> { setInputSearchTerm(value); updateServerList(allServers, value); }} sx={{ marginRight: '.6em' }} value={inputSearchTerm} /> setIsOpenProvisionServerDialog(true)}> {createServerPreviewContainer(includeServers, router)} {includeServers.length > 0 && ( )} {createServerPreviewContainer(excludeServers, router)} )} { setIsOpenProvisionServerDialog(false); }} /> ); }; export default Dashboard;