From 6b52a36b3165c2062ad644e9cbed48a100a337ab Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 3 Oct 2020 19:36:26 -0700 Subject: [PATCH] Significantly reduce the number of re-renders on the console page when stats are flowing --- .../components/server/PowerControls.tsx | 55 ++++++++ .../components/server/ServerConsole.tsx | 118 ++---------------- .../components/server/ServerDetailsBlock.tsx | 83 ++++++++++++ resources/scripts/routers/ServerRouter.tsx | 2 +- 4 files changed, 148 insertions(+), 110 deletions(-) create mode 100644 resources/scripts/components/server/PowerControls.tsx create mode 100644 resources/scripts/components/server/ServerDetailsBlock.tsx diff --git a/resources/scripts/components/server/PowerControls.tsx b/resources/scripts/components/server/PowerControls.tsx new file mode 100644 index 000000000..e21669f54 --- /dev/null +++ b/resources/scripts/components/server/PowerControls.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import tw from 'twin.macro'; +import Can from '@/components/elements/Can'; +import Button from '@/components/elements/Button'; +import StopOrKillButton from '@/components/server/StopOrKillButton'; +import { PowerAction } from '@/components/server/ServerConsole'; +import { ServerContext } from '@/state/server'; + +const PowerControls = () => { + const status = ServerContext.useStoreState(state => state.status.value); + const instance = ServerContext.useStoreState(state => state.socket.instance); + + const sendPowerCommand = (command: PowerAction) => { + instance && instance.send('set state', command); + }; + + return ( +
+ + + + + + + + sendPowerCommand(action)}/> + +
+ ) +}; + +export default PowerControls; diff --git a/resources/scripts/components/server/ServerConsole.tsx b/resources/scripts/components/server/ServerConsole.tsx index edf10d130..ed24d4aa9 100644 --- a/resources/scripts/components/server/ServerConsole.tsx +++ b/resources/scripts/components/server/ServerConsole.tsx @@ -1,131 +1,29 @@ -import React, { lazy, useEffect, useState } from 'react'; +import React, { lazy, memo } from 'react'; import { ServerContext } from '@/state/server'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCircle, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons'; -import { bytesToHuman, megabytesToHuman } from '@/helpers'; import SuspenseSpinner from '@/components/elements/SuspenseSpinner'; -import TitledGreyBox from '@/components/elements/TitledGreyBox'; import Can from '@/components/elements/Can'; import ContentContainer from '@/components/elements/ContentContainer'; import tw from 'twin.macro'; -import Button from '@/components/elements/Button'; -import StopOrKillButton from '@/components/server/StopOrKillButton'; import ServerContentBlock from '@/components/elements/ServerContentBlock'; +import ServerDetailsBlock from '@/components/server/ServerDetailsBlock'; +import isEqual from 'react-fast-compare'; +import PowerControls from '@/components/server/PowerControls'; export type PowerAction = 'start' | 'stop' | 'restart' | 'kill'; const ChunkedConsole = lazy(() => import(/* webpackChunkName: "console" */'@/components/server/Console')); const ChunkedStatGraphs = lazy(() => import(/* webpackChunkName: "graphs" */'@/components/server/StatGraphs')); -export default () => { - const [ memory, setMemory ] = useState(0); - const [ cpu, setCpu ] = useState(0); - const [ disk, setDisk ] = useState(0); - - const name = ServerContext.useStoreState(state => state.server.data!.name); - const limits = ServerContext.useStoreState(state => state.server.data!.limits); +const ServerConsole = () => { const isInstalling = ServerContext.useStoreState(state => state.server.data!.isInstalling); - const status = ServerContext.useStoreState(state => state.status.value); - - const connected = ServerContext.useStoreState(state => state.socket.connected); - const instance = ServerContext.useStoreState(state => state.socket.instance); - - const statsListener = (data: string) => { - let stats: any = {}; - try { - stats = JSON.parse(data); - } catch (e) { - return; - } - - setMemory(stats.memory_bytes); - setCpu(stats.cpu_absolute); - setDisk(stats.disk_bytes); - }; - - const sendPowerCommand = (command: PowerAction) => { - instance && instance.send('set state', command); - }; - - useEffect(() => { - if (!connected || !instance) { - return; - } - - instance.addListener('stats', statsListener); - instance.send('send stats'); - - return () => { - instance.removeListener('stats', statsListener); - }; - }, [ instance, connected ]); - - const disklimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited'; - const memorylimit = limits.memory ? megabytesToHuman(limits.memory) : 'Unlimited'; return (
- -

- -  {!status ? 'Connecting...' : status} -

-

- {cpu.toFixed(2)}% -

-

- {bytesToHuman(memory)} - / {memorylimit} -

-

-  {bytesToHuman(disk)} - / {disklimit} -

-
+ {!isInstalling ? -
- - - - - - - - sendPowerCommand(action)}/> - -
+
:
@@ -147,3 +45,5 @@ export default () => { ); }; + +export default memo(ServerConsole, isEqual); diff --git a/resources/scripts/components/server/ServerDetailsBlock.tsx b/resources/scripts/components/server/ServerDetailsBlock.tsx new file mode 100644 index 000000000..563dedf76 --- /dev/null +++ b/resources/scripts/components/server/ServerDetailsBlock.tsx @@ -0,0 +1,83 @@ +import React, { useEffect, useState } from 'react'; +import tw from 'twin.macro'; +import { faCircle, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { bytesToHuman, megabytesToHuman } from '@/helpers'; +import TitledGreyBox from '@/components/elements/TitledGreyBox'; +import { ServerContext } from '@/state/server'; + +interface Stats { + memory: number; + cpu: number; + disk: number; +} + +const ServerDetailsBlock = () => { + const [ stats, setStats ] = useState({ memory: 0, cpu: 0, disk: 0 }); + + const connected = ServerContext.useStoreState(state => state.socket.connected); + const instance = ServerContext.useStoreState(state => state.socket.instance); + + const statsListener = (data: string) => { + let stats: any = {}; + try { + stats = JSON.parse(data); + } catch (e) { + return; + } + + setStats({ + memory: stats.memory_bytes, + cpu: stats.cpu_absolute, + disk: stats.disk_bytes, + }); + }; + + useEffect(() => { + if (!connected || !instance) { + return; + } + + instance.addListener('stats', statsListener); + instance.send('send stats'); + + return () => { + instance.removeListener('stats', statsListener); + }; + }, [ instance, connected ]); + + const name = ServerContext.useStoreState(state => state.server.data!.name); + const limits = ServerContext.useStoreState(state => state.server.data!.limits); + + const disklimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited'; + const memorylimit = limits.memory ? megabytesToHuman(limits.memory) : 'Unlimited'; + + return ( + +

+ +  {!status ? 'Connecting...' : status} +

+

+ {stats.cpu.toFixed(2)}% +

+

+ {bytesToHuman(stats.memory)} + / {memorylimit} +

+

+  {bytesToHuman(stats.disk)} + / {disklimit} +

+
+ ); +}; + +export default ServerDetailsBlock; diff --git a/resources/scripts/routers/ServerRouter.tsx b/resources/scripts/routers/ServerRouter.tsx index b830a3c7a..a90ff652b 100644 --- a/resources/scripts/routers/ServerRouter.tsx +++ b/resources/scripts/routers/ServerRouter.tsx @@ -30,7 +30,7 @@ import StartupContainer from '@/components/server/startup/StartupContainer'; import requireServerPermission from '@/hoc/requireServerPermission'; const ServerRouter = ({ match, location }: RouteComponentProps<{ id: string }>) => { - const { rootAdmin } = useStoreState(state => state.user.data!); + const rootAdmin = useStoreState(state => state.user.data!.rootAdmin); const [ error, setError ] = useState(''); const [ installing, setInstalling ] = useState(false);