Update logic to listen for a restoration completion event

This commit is contained in:
Dane Everitt 2021-01-30 18:43:46 -08:00
parent f558bc880a
commit c8ecbe6e79
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
9 changed files with 68 additions and 48 deletions

View file

@ -13,6 +13,7 @@ import 'xterm/css/xterm.css';
import useEventListener from '@/plugins/useEventListener'; import useEventListener from '@/plugins/useEventListener';
import { debounce } from 'debounce'; import { debounce } from 'debounce';
import { usePersistedState } from '@/plugins/usePersistedState'; import { usePersistedState } from '@/plugins/usePersistedState';
import { SocketEvent, SocketRequest } from '@/components/server/events';
const theme = { const theme = {
background: th`colors.black`.toString(), background: th`colors.black`.toString(),
@ -172,32 +173,35 @@ export default () => {
useEventListener('resize', () => fit()); useEventListener('resize', () => fit());
useEffect(() => { useEffect(() => {
const listeners: Record<string, (s: string) => void> = {
[SocketEvent.STATUS]: handlePowerChangeEvent,
[SocketEvent.CONSOLE_OUTPUT]: handleConsoleOutput,
[SocketEvent.INSTALL_OUTPUT]: handleConsoleOutput,
[SocketEvent.TRANSFER_LOGS]: handleConsoleOutput,
[SocketEvent.TRANSFER_STATUS]: handleTransferStatus,
[SocketEvent.DAEMON_MESSAGE]: line => handleConsoleOutput(line, true),
[SocketEvent.DAEMON_ERROR]: handleDaemonErrorOutput,
};
if (connected && instance) { if (connected && instance) {
// Do not clear the console if the server is being transferred. // Do not clear the console if the server is being transferred.
if (!isTransferring) { if (!isTransferring) {
terminal.clear(); terminal.clear();
} }
instance.addListener('status', handlePowerChangeEvent); Object.keys(listeners).forEach((key: string) => {
instance.addListener('console output', handleConsoleOutput); instance.addListener(key, listeners[key]);
instance.addListener('install output', handleConsoleOutput); });
instance.addListener('transfer logs', handleConsoleOutput); instance.send(SocketRequest.SEND_LOGS);
instance.addListener('transfer status', handleTransferStatus);
instance.addListener('daemon message', line => handleConsoleOutput(line, true));
instance.addListener('daemon error', handleDaemonErrorOutput);
instance.send('send logs');
} }
return () => { return () => {
instance && instance.removeListener('status', handlePowerChangeEvent) if (instance) {
.removeListener('console output', handleConsoleOutput) Object.keys(listeners).forEach((key: string) => {
.removeListener('install output', handleConsoleOutput) instance.removeListener(key, listeners[key]);
.removeListener('transfer logs', handleConsoleOutput) });
.removeListener('transfer status', handleTransferStatus) }
.removeListener('daemon message', line => handleConsoleOutput(line, true))
.removeListener('daemon error', handleDaemonErrorOutput);
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ connected, instance ]); }, [ connected, instance ]);
return ( return (

View file

@ -1,22 +1,30 @@
import useWebsocketEvent from '@/plugins/useWebsocketEvent'; import useWebsocketEvent from '@/plugins/useWebsocketEvent';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import { SocketEvent } from '@/components/server/events';
import useFileManagerSwr from '@/plugins/useFileManagerSwr';
const InstallListener = () => { const InstallListener = () => {
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
const getServer = ServerContext.useStoreActions(actions => actions.server.getServer); const getServer = ServerContext.useStoreActions(actions => actions.server.getServer);
const { mutate } = useFileManagerSwr();
const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState);
useWebsocketEvent(SocketEvent.BACKUP_RESTORE_COMPLETED, () => {
mutate(undefined);
setServerFromState(s => ({ ...s, status: null }));
});
// Listen for the installation completion event and then fire off a request to fetch the updated // Listen for the installation completion event and then fire off a request to fetch the updated
// server information. This allows the server to automatically become available to the user if they // server information. This allows the server to automatically become available to the user if they
// just sit on the page. // just sit on the page.
useWebsocketEvent('install completed', () => { useWebsocketEvent(SocketEvent.INSTALL_COMPLETED, () => {
getServer(uuid).catch(error => console.error(error)); getServer(uuid).catch(error => console.error(error));
}); });
// When we see the install started event immediately update the state to indicate such so that the // When we see the install started event immediately update the state to indicate such so that the
// screens automatically update. // screens automatically update.
useWebsocketEvent('install started', () => { useWebsocketEvent(SocketEvent.INSTALL_STARTED, () => {
setServerFromState(s => ({ ...s, isInstalling: true })); setServerFromState(s => ({ ...s, status: 'installing' }));
}); });
return null; return null;

View file

@ -6,6 +6,7 @@ import { bytesToHuman, megabytesToHuman } from '@/helpers';
import TitledGreyBox from '@/components/elements/TitledGreyBox'; import TitledGreyBox from '@/components/elements/TitledGreyBox';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import CopyOnClick from '@/components/elements/CopyOnClick'; import CopyOnClick from '@/components/elements/CopyOnClick';
import { SocketEvent, SocketRequest } from '@/components/server/events';
interface Stats { interface Stats {
memory: number; memory: number;
@ -55,11 +56,11 @@ const ServerDetailsBlock = () => {
return; return;
} }
instance.addListener('stats', statsListener); instance.addListener(SocketEvent.STATS, statsListener);
instance.send('send stats'); instance.send(SocketRequest.SEND_STATS);
return () => { return () => {
instance.removeListener('stats', statsListener); instance.removeListener(SocketEvent.STATS, statsListener);
}; };
}, [ instance, connected ]); }, [ instance, connected ]);

View file

@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useState } from 'react';
import Chart, { ChartConfiguration } from 'chart.js'; import Chart, { ChartConfiguration } from 'chart.js';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import { bytesToMegabytes } from '@/helpers'; import { bytesToMegabytes } from '@/helpers';
@ -6,6 +6,8 @@ import merge from 'deepmerge';
import TitledGreyBox from '@/components/elements/TitledGreyBox'; import TitledGreyBox from '@/components/elements/TitledGreyBox';
import { faMemory, faMicrochip } from '@fortawesome/free-solid-svg-icons'; import { faMemory, faMicrochip } from '@fortawesome/free-solid-svg-icons';
import tw from 'twin.macro'; import tw from 'twin.macro';
import { SocketEvent } from '@/components/server/events';
import useWebsocketEvent from '@/plugins/useWebsocketEvent';
const chartDefaults = (ticks?: Chart.TickOptions | undefined): ChartConfiguration => ({ const chartDefaults = (ticks?: Chart.TickOptions | undefined): ChartConfiguration => ({
type: 'line', type: 'line',
@ -70,7 +72,6 @@ const chartDefaults = (ticks?: Chart.TickOptions | undefined): ChartConfiguratio
export default () => { export default () => {
const status = ServerContext.useStoreState(state => state.status.value); const status = ServerContext.useStoreState(state => state.status.value);
const limits = ServerContext.useStoreState(state => state.server.data!.limits); const limits = ServerContext.useStoreState(state => state.server.data!.limits);
const { connected, instance } = ServerContext.useStoreState(state => state.socket);
const [ memory, setMemory ] = useState<Chart>(); const [ memory, setMemory ] = useState<Chart>();
const [ cpu, setCpu ] = useState<Chart>(); const [ cpu, setCpu ] = useState<Chart>();
@ -84,7 +85,7 @@ export default () => {
new Chart(node.getContext('2d')!, chartDefaults({ new Chart(node.getContext('2d')!, chartDefaults({
callback: (value) => `${value}Mb `, callback: (value) => `${value}Mb `,
suggestedMax: limits.memory, suggestedMax: limits.memory,
})) })),
); );
}, []); }, []);
@ -100,7 +101,7 @@ export default () => {
); );
}, []); }, []);
const statsListener = (data: string) => { useWebsocketEvent(SocketEvent.STATS, (data: string) => {
let stats: any = {}; let stats: any = {};
try { try {
stats = JSON.parse(data); stats = JSON.parse(data);
@ -125,27 +126,19 @@ export default () => {
cpu.update({ lazy: true }); cpu.update({ lazy: true });
} }
}; });
useEffect(() => {
if (!connected || !instance) {
return;
}
instance.addListener('stats', statsListener);
return () => {
instance.removeListener('stats', statsListener);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ instance, connected, memory, cpu ]);
return ( return (
<div css={tw`flex flex-wrap mt-4`}> <div css={tw`flex flex-wrap mt-4`}>
<div css={tw`w-full sm:w-1/2`}> <div css={tw`w-full sm:w-1/2`}>
<TitledGreyBox title={'Memory usage'} icon={faMemory} css={tw`mr-0 sm:mr-4`}> <TitledGreyBox title={'Memory usage'} icon={faMemory} css={tw`mr-0 sm:mr-4`}>
{status !== 'offline' ? {status !== 'offline' ?
<canvas id={'memory_chart'} ref={memoryRef} aria-label={'Server Memory Usage Graph'} role={'img'}/> <canvas
id={'memory_chart'}
ref={memoryRef}
aria-label={'Server Memory Usage Graph'}
role={'img'}
/>
: :
<p css={tw`text-xs text-neutral-400 text-center p-3`}> <p css={tw`text-xs text-neutral-400 text-center p-3`}>
Server is offline. Server is offline.

View file

@ -1,5 +1,6 @@
import useWebsocketEvent from '@/plugins/useWebsocketEvent'; import useWebsocketEvent from '@/plugins/useWebsocketEvent';
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import { SocketEvent } from '@/components/server/events';
const TransferListener = () => { const TransferListener = () => {
const uuid = ServerContext.useStoreState(state => state.server.data!.uuid); const uuid = ServerContext.useStoreState(state => state.server.data!.uuid);
@ -7,7 +8,7 @@ const TransferListener = () => {
const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState); const setServerFromState = ServerContext.useStoreActions(actions => actions.server.setServerFromState);
// Listen for the transfer status event so we can update the state of the server. // Listen for the transfer status event so we can update the state of the server.
useWebsocketEvent('transfer status', (status: string) => { useWebsocketEvent(SocketEvent.TRANSFER_STATUS, (status: string) => {
if (status === 'starting') { if (status === 'starting') {
setServerFromState(s => ({ ...s, isTransferring: true })); setServerFromState(s => ({ ...s, isTransferring: true }));
return; return;

View file

@ -11,6 +11,7 @@ import tw from 'twin.macro';
import GreyRowBox from '@/components/elements/GreyRowBox'; import GreyRowBox from '@/components/elements/GreyRowBox';
import getServerBackups from '@/api/swr/getServerBackups'; import getServerBackups from '@/api/swr/getServerBackups';
import { ServerBackup } from '@/api/server/types'; import { ServerBackup } from '@/api/server/types';
import { SocketEvent } from '@/components/server/events';
interface Props { interface Props {
backup: ServerBackup; backup: ServerBackup;
@ -20,7 +21,7 @@ interface Props {
export default ({ backup, className }: Props) => { export default ({ backup, className }: Props) => {
const { mutate } = getServerBackups(); const { mutate } = getServerBackups();
useWebsocketEvent(`backup completed:${backup.uuid}`, data => { useWebsocketEvent(`${SocketEvent.BACKUP_COMPLETED}:${backup.uuid}` as SocketEvent, data => {
try { try {
const parsed = JSON.parse(data); const parsed = JSON.parse(data);

View file

@ -1,10 +1,20 @@
export enum SocketEvent { export enum SocketEvent {
DAEMON_MESSAGE = 'daemon message', DAEMON_MESSAGE = 'daemon message',
DAEMON_ERROR = 'daemon error',
INSTALL_OUTPUT = 'install output', INSTALL_OUTPUT = 'install output',
INSTALL_STARTED = 'install started', INSTALL_STARTED = 'install started',
INSTALL_COMPLETED = 'install completed', INSTALL_COMPLETED = 'install completed',
CONSOLE_OUTPUT = 'console output', CONSOLE_OUTPUT = 'console output',
STATUS = 'status', STATUS = 'status',
STATS = 'stats', STATS = 'stats',
TRANSFER_LOGS = 'transfer logs',
TRANSFER_STATUS = 'transfer status',
BACKUP_COMPLETED = 'backup completed', BACKUP_COMPLETED = 'backup completed',
BACKUP_RESTORE_COMPLETED = 'backup restore completed',
}
export enum SocketRequest {
SEND_LOGS = 'send logs',
SEND_STATS = 'send stats',
SET_STATE = 'set state'
} }

View file

@ -6,6 +6,7 @@ import Button from '@/components/elements/Button';
import saveFileContents from '@/api/server/files/saveFileContents'; import saveFileContents from '@/api/server/files/saveFileContents';
import FlashMessageRender from '@/components/FlashMessageRender'; import FlashMessageRender from '@/components/FlashMessageRender';
import useFlash from '@/plugins/useFlash'; import useFlash from '@/plugins/useFlash';
import { SocketEvent, SocketRequest } from '@/components/server/events';
const EulaModalFeature = () => { const EulaModalFeature = () => {
const [ visible, setVisible ] = useState(false); const [ visible, setVisible ] = useState(false);
@ -25,10 +26,10 @@ const EulaModalFeature = () => {
} }
}; };
instance.addListener('console output', listener); instance.addListener(SocketEvent.CONSOLE_OUTPUT, listener);
return () => { return () => {
instance.removeListener('console output', listener); instance.removeListener(SocketEvent.CONSOLE_OUTPUT, listener);
}; };
}, [ connected, instance, status ]); }, [ connected, instance, status ]);
@ -39,7 +40,7 @@ const EulaModalFeature = () => {
saveFileContents(uuid, 'eula.txt', 'eula=true') saveFileContents(uuid, 'eula.txt', 'eula=true')
.then(() => { .then(() => {
if (status === 'offline' && instance) { if (status === 'offline' && instance) {
instance.send('set state', 'restart'); instance.send(SocketRequest.SET_STATE, 'restart');
} }
setLoading(false); setLoading(false);

View file

@ -1,7 +1,8 @@
import { ServerContext } from '@/state/server'; import { ServerContext } from '@/state/server';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { SocketEvent } from '@/components/server/events';
const useWebsocketEvent = (event: string, callback: (data: string) => void) => { const useWebsocketEvent = (event: SocketEvent, callback: (data: string) => void) => {
const { connected, instance } = ServerContext.useStoreState(state => state.socket); const { connected, instance } = ServerContext.useStoreState(state => state.socket);
const savedCallback = useRef<any>(null); const savedCallback = useRef<any>(null);
@ -10,7 +11,7 @@ const useWebsocketEvent = (event: string, callback: (data: string) => void) => {
}, [ callback ]); }, [ callback ]);
return useEffect(() => { return useEffect(() => {
const eventListener = (event: any) => savedCallback.current(event); const eventListener = (event: SocketEvent) => savedCallback.current(event);
if (connected && instance) { if (connected && instance) {
instance.addListener(event, eventListener); instance.addListener(event, eventListener);
} }