fix(striker-ui): correct server screenshot loading, stale, display

main
Tsu-ba-me 1 year ago
parent bd33f8711f
commit 813767f85a
  1. 90
      striker-ui/components/Display/Preview.tsx
  2. 2
      striker-ui/components/Servers.tsx
  3. 23
      striker-ui/pages/index.tsx
  4. 7
      striker-ui/pages/server/index.tsx

@ -1,6 +1,6 @@
import { import {
DesktopWindows as DesktopWindowsIcon, DesktopWindows as DesktopWindowsIcon,
PowerOffOutlined as PowerOffOutlinedIcon, PowerSettingsNewOutlined as PowerSettingsNewOutlinedIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { import {
Box, Box,
@ -9,18 +9,24 @@ import {
} from '@mui/material'; } from '@mui/material';
import { FC, ReactNode, useEffect, useMemo, useState } from 'react'; import { FC, ReactNode, useEffect, useMemo, useState } from 'react';
import API_BASE_URL from '../../lib/consts/API_BASE_URL'; import {
import { BORDER_RADIUS, GREY } from '../../lib/consts/DEFAULT_THEME'; BORDER_RADIUS,
GREY,
UNSELECTED,
} from '../../lib/consts/DEFAULT_THEME';
import api from '../../lib/api';
import FlexBox from '../FlexBox'; import FlexBox from '../FlexBox';
import IconButton, { IconButtonProps } from '../IconButton'; import IconButton, { IconButtonProps } from '../IconButton';
import { InnerPanel, InnerPanelHeader, Panel, PanelHeader } from '../Panels'; import { InnerPanel, InnerPanelHeader, Panel, PanelHeader } from '../Panels';
import Spinner from '../Spinner'; import Spinner from '../Spinner';
import { BodyText, HeaderText } from '../Text'; import { BodyText, HeaderText } from '../Text';
import { last } from '../../lib/time';
type PreviewOptionalProps = { type PreviewOptionalProps = {
externalPreview?: string; externalPreview?: string;
headerEndAdornment?: ReactNode; headerEndAdornment?: ReactNode;
isExternalLoading?: boolean;
isExternalPreviewStale?: boolean; isExternalPreviewStale?: boolean;
isFetchPreview?: boolean; isFetchPreview?: boolean;
isShowControls?: boolean; isShowControls?: boolean;
@ -28,6 +34,7 @@ type PreviewOptionalProps = {
onClickConnectButton?: IconButtonProps['onClick']; onClickConnectButton?: IconButtonProps['onClick'];
onClickPreview?: MUIIconButtonProps['onClick']; onClickPreview?: MUIIconButtonProps['onClick'];
serverName?: string; serverName?: string;
serverState?: string;
}; };
type PreviewProps = PreviewOptionalProps & { type PreviewProps = PreviewOptionalProps & {
@ -40,6 +47,7 @@ const PREVIEW_DEFAULT_PROPS: Required<
Pick<PreviewOptionalProps, 'onClickConnectButton' | 'onClickPreview'> = { Pick<PreviewOptionalProps, 'onClickConnectButton' | 'onClickPreview'> = {
externalPreview: '', externalPreview: '',
headerEndAdornment: null, headerEndAdornment: null,
isExternalLoading: false,
isExternalPreviewStale: false, isExternalPreviewStale: false,
isFetchPreview: true, isFetchPreview: true,
isShowControls: true, isShowControls: true,
@ -47,6 +55,7 @@ const PREVIEW_DEFAULT_PROPS: Required<
onClickConnectButton: undefined, onClickConnectButton: undefined,
onClickPreview: undefined, onClickPreview: undefined,
serverName: '', serverName: '',
serverState: '',
}; };
const PreviewPanel: FC<{ isUseInnerPanel: boolean }> = ({ const PreviewPanel: FC<{ isUseInnerPanel: boolean }> = ({
@ -78,12 +87,14 @@ const PreviewPanelHeader: FC<{
const Preview: FC<PreviewProps> = ({ const Preview: FC<PreviewProps> = ({
externalPreview = PREVIEW_DEFAULT_PROPS.externalPreview, externalPreview = PREVIEW_DEFAULT_PROPS.externalPreview,
headerEndAdornment, headerEndAdornment,
isExternalLoading = PREVIEW_DEFAULT_PROPS.isExternalLoading,
isExternalPreviewStale = PREVIEW_DEFAULT_PROPS.isExternalPreviewStale, isExternalPreviewStale = PREVIEW_DEFAULT_PROPS.isExternalPreviewStale,
isFetchPreview = PREVIEW_DEFAULT_PROPS.isFetchPreview, isFetchPreview = PREVIEW_DEFAULT_PROPS.isFetchPreview,
isShowControls = PREVIEW_DEFAULT_PROPS.isShowControls, isShowControls = PREVIEW_DEFAULT_PROPS.isShowControls,
isUseInnerPanel = PREVIEW_DEFAULT_PROPS.isUseInnerPanel, isUseInnerPanel = PREVIEW_DEFAULT_PROPS.isUseInnerPanel,
onClickPreview: previewClickHandler, onClickPreview: previewClickHandler,
serverName, serverName,
serverState = PREVIEW_DEFAULT_PROPS.serverState,
serverUUID, serverUUID,
onClickConnectButton: connectButtonClickHandle = previewClickHandler, onClickConnectButton: connectButtonClickHandle = previewClickHandler,
}) => { }) => {
@ -93,58 +104,66 @@ const Preview: FC<PreviewProps> = ({
const previewButtonContent = useMemo( const previewButtonContent = useMemo(
() => () =>
preview ? ( serverState === 'running' ? (
<Box <>
alt="" <Box
component="img" alt=""
src={`data:image/png;base64,${preview}`} component="img"
sx={{ src={`data:image;base64,${preview}`}
height: '100%', sx={{
opacity: isPreviewStale ? '0.4' : '1', height: '100%',
padding: isUseInnerPanel ? '.2em' : 0, opacity: isPreviewStale ? '0.4' : '1',
width: '100%', padding: isUseInnerPanel ? '.2em' : 0,
}} width: '100%',
/> }}
/>
{isPreviewStale && (
<BodyText sx={{ position: 'absolute' }}>Outdated</BodyText>
)}
</>
) : ( ) : (
<PowerOffOutlinedIcon <PowerSettingsNewOutlinedIcon
sx={{ sx={{
height: '100%', color: UNSELECTED,
width: '100%', height: '80%',
width: '80%',
}} }}
/> />
), ),
[isPreviewStale, isUseInnerPanel, preview], [isPreviewStale, isUseInnerPanel, preview, serverState],
); );
useEffect(() => { useEffect(() => {
if (isFetchPreview) { if (isFetchPreview) {
(async () => { (async () => {
try { try {
const response = await fetch( const { data } = await api.get<{
`${API_BASE_URL}/server/${serverUUID}?ss=1`, screenshot: string;
{ timestamp: number;
method: 'GET', }>(`/server/${serverUUID}?ss=1`);
headers: {
'Content-Type': 'application/json', const { screenshot, timestamp } = data;
},
},
);
const { screenshot: fetchedScreenshot } = await response.json();
setPreview(fetchedScreenshot); setPreview(screenshot);
setIsPreviewStale(false); setIsPreviewStale(!last(timestamp, 300));
} catch { } catch {
setIsPreviewStale(true); setIsPreviewStale(true);
} finally { } finally {
setIsPreviewLoading(false); setIsPreviewLoading(false);
} }
})(); })();
} else if (externalPreview) { } else if (!isExternalLoading) {
setPreview(externalPreview); setPreview(externalPreview);
setIsPreviewStale(isExternalPreviewStale); setIsPreviewStale(isExternalPreviewStale);
setIsPreviewLoading(false); setIsPreviewLoading(false);
} }
}, [externalPreview, isExternalPreviewStale, isFetchPreview, serverUUID]); }, [
externalPreview,
isExternalLoading,
isExternalPreviewStale,
isFetchPreview,
serverUUID,
]);
return ( return (
<PreviewPanel isUseInnerPanel={isUseInnerPanel}> <PreviewPanel isUseInnerPanel={isUseInnerPanel}>
@ -153,12 +172,13 @@ const Preview: FC<PreviewProps> = ({
</PreviewPanelHeader> </PreviewPanelHeader>
<FlexBox row sx={{ '& > :first-child': { flexGrow: 1 } }}> <FlexBox row sx={{ '& > :first-child': { flexGrow: 1 } }}>
{/* Box wrapper below is required to keep external preview size sane. */} {/* Box wrapper below is required to keep external preview size sane. */}
<Box> <Box textAlign="center">
{isPreviewLoading ? ( {isPreviewLoading ? (
<Spinner mt="1em" mb="1em" /> <Spinner mt="1em" mb="1em" />
) : ( ) : (
<MUIIconButton <MUIIconButton
component="span" component="span"
disabled={!preview}
onClick={previewClickHandler} onClick={previewClickHandler}
sx={{ sx={{
borderRadius: BORDER_RADIUS, borderRadius: BORDER_RADIUS,
@ -170,7 +190,7 @@ const Preview: FC<PreviewProps> = ({
</MUIIconButton> </MUIIconButton>
)} )}
</Box> </Box>
{isShowControls && ( {isShowControls && preview && (
<FlexBox> <FlexBox>
<IconButton onClick={connectButtonClickHandle}> <IconButton onClick={connectButtonClickHandle}>
<DesktopWindowsIcon /> <DesktopWindowsIcon />

@ -335,7 +335,7 @@ const Servers = ({ anvil }: { anvil: AnvilListItem[] }): JSX.Element => {
className={classes.button} className={classes.button}
key={server.serverUUID} key={server.serverUUID}
component={showCheckbox ? 'div' : 'a'} component={showCheckbox ? 'div' : 'a'}
href={`/server?uuid=${server.serverUUID}&server_name=${server.serverName}`} href={`/server?uuid=${server.serverUUID}&server_name=${server.serverName}&server_state=${server.serverState}`}
onClick={() => handleChange(server.serverUUID)} onClick={() => handleChange(server.serverUUID)}
> >
<Box display="flex" flexDirection="row" width="100%"> <Box display="flex" flexDirection="row" width="100%">

@ -17,9 +17,11 @@ import { Panel, PanelHeader } from '../components/Panels';
import periodicFetch from '../lib/fetchers/periodicFetch'; import periodicFetch from '../lib/fetchers/periodicFetch';
import ProvisionServerDialog from '../components/ProvisionServerDialog'; import ProvisionServerDialog from '../components/ProvisionServerDialog';
import Spinner from '../components/Spinner'; import Spinner from '../components/Spinner';
import { last } from '../lib/time';
type ServerListItem = ServerOverviewMetadata & { type ServerListItem = ServerOverviewMetadata & {
isScreenshotStale?: boolean; isScreenshotStale?: boolean;
loading?: boolean;
screenshot: string; screenshot: string;
}; };
@ -47,15 +49,17 @@ const createServerPreviewContainer = (
anvilName, anvilName,
anvilUUID, anvilUUID,
isScreenshotStale, isScreenshotStale,
loading,
screenshot, screenshot,
serverName, serverName,
serverState,
serverUUID, serverUUID,
}) => ( }) => (
<Preview <Preview
externalPreview={screenshot} externalPreview={screenshot}
headerEndAdornment={[ headerEndAdornment={[
<Link <Link
href={`/server?uuid=${serverUUID}&server_name=${serverName}`} href={`/server?uuid=${serverUUID}&server_name=${serverName}&server_state=${serverState}`}
key={`server_list_to_server_${serverUUID}`} key={`server_list_to_server_${serverUUID}`}
> >
{serverName} {serverName}
@ -70,6 +74,7 @@ const createServerPreviewContainer = (
{anvilName} {anvilName}
</Link>, </Link>,
]} ]}
isExternalLoading={loading}
isExternalPreviewStale={isScreenshotStale} isExternalPreviewStale={isScreenshotStale}
isFetchPreview={false} isFetchPreview={false}
isShowControls={false} isShowControls={false}
@ -77,9 +82,10 @@ const createServerPreviewContainer = (
key={`server-preview-${serverUUID}`} key={`server-preview-${serverUUID}`}
onClickPreview={() => { onClickPreview={() => {
router.push( router.push(
`/server?uuid=${serverUUID}&server_name=${serverName}&vnc=1`, `/server?uuid=${serverUUID}&server_name=${serverName}&server_state=${serverState}&vnc=1`,
); );
}} }}
serverState={serverState}
serverUUID={serverUUID} serverUUID={serverUUID}
/> />
), ),
@ -150,15 +156,19 @@ const Dashboard: FC = () => {
?.screenshot || ''; ?.screenshot || '';
const item: ServerListItem = { const item: ServerListItem = {
...serverOverview, ...serverOverview,
loading: true,
screenshot: previousScreenshot, screenshot: previousScreenshot,
}; };
fetchJSON<{ screenshot: string }>( fetchJSON<{ screenshot: string; timestamp: number }>(
`${API_BASE_URL}/server/${serverUUID}?ss=1`, `${API_BASE_URL}/server/${serverUUID}?ss=1`,
) )
.then(({ screenshot }) => { .then(({ screenshot, timestamp }) => {
if (screenshot.length === 0) return;
item.isScreenshotStale = !last(timestamp, 300);
item.loading = false;
item.screenshot = screenshot; item.screenshot = screenshot;
item.isScreenshotStale = false;
const allServersWithScreenshots = [...serverListItems]; const allServersWithScreenshots = [...serverListItems];
@ -174,6 +184,9 @@ const Dashboard: FC = () => {
}) })
.catch(() => { .catch(() => {
item.isScreenshotStale = true; item.isScreenshotStale = true;
})
.finally(() => {
item.loading = false;
}); });
return item; return item;

@ -34,10 +34,12 @@ const Server = (): JSX.Element => {
const [previewMode, setPreviewMode] = useState<boolean>(true); const [previewMode, setPreviewMode] = useState<boolean>(true);
const router = useRouter(); const router = useRouter();
const { server_name, uuid, vnc } = router.query; const { server_name, server_state, uuid, vnc } = router.query;
const isConnectVNC: boolean = (vnc?.toString() || '').length > 0; const isConnectVNC: boolean = (vnc?.toString() || '').length > 0;
const serverUUID: string = uuid?.toString() || '';
const serverName: string = server_name?.toString() || ''; const serverName: string = server_name?.toString() || '';
const serverState: string = server_state?.toString() || '';
const serverUUID: string = uuid?.toString() || '';
useEffect(() => { useEffect(() => {
if (isConnectVNC) { if (isConnectVNC) {
@ -58,6 +60,7 @@ const Server = (): JSX.Element => {
setPreviewMode(false); setPreviewMode(false);
}} }}
serverName={serverName} serverName={serverName}
serverState={serverState}
serverUUID={serverUUID} serverUUID={serverUUID}
/> />
</Box> </Box>

Loading…
Cancel
Save