Update server listing and associated logic to pull from the panel dynamiacally

This commit is contained in:
Dane Everitt 2019-08-17 16:03:10 -07:00
parent 952dff854e
commit fb9c106448
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
26 changed files with 384 additions and 239 deletions

View file

@ -1,97 +1,35 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faServer } from '@fortawesome/free-solid-svg-icons/faServer';
import { faMicrochip } from '@fortawesome/free-solid-svg-icons/faMicrochip';
import { faMemory } from '@fortawesome/free-solid-svg-icons/faMemory';
import { faHdd } from '@fortawesome/free-solid-svg-icons/faHdd';
import { faEthernet } from '@fortawesome/free-solid-svg-icons/faEthernet';
import { Link } from 'react-router-dom';
import { Server } from '@/api/server/getServer';
import getServers from '@/api/getServers';
import ServerRow from '@/components/dashboard/ServerRow';
import Spinner from '@/components/elements/Spinner';
export default () => (
<div className={'my-10'}>
<Link to={'/server/e9d6c836'} className={'grey-row-box cursor-pointer'}>
<div className={'icon'}>
<FontAwesomeIcon icon={faServer}/>
</div>
<div className={'w-1/2 ml-4'}>
<p className={'text-lg'}>Party Parrots</p>
</div>
<div className={'flex flex-1 items-baseline justify-around'}>
<div className={'flex ml-4'}>
<FontAwesomeIcon icon={faEthernet} className={'text-neutral-500'}/>
<p className={'text-sm text-neutral-400 ml-2'}>
192.168.100.100:25565
</p>
</div>
<div className={'flex ml-4'}>
<FontAwesomeIcon icon={faMicrochip} className={'text-neutral-500'}/>
<p className={'text-sm text-neutral-400 ml-2'}>
34.6%
</p>
</div>
<div className={'ml-4'}>
<div className={'flex'}>
<FontAwesomeIcon icon={faMemory} className={'text-neutral-500'}/>
<p className={'text-sm text-neutral-400 ml-2'}>
2094 MB
</p>
</div>
<p className={'text-xs text-neutral-600 text-center mt-1'}>of 4096 MB</p>
</div>
<div className={'ml-4'}>
<div className={'flex'}>
<FontAwesomeIcon icon={faHdd} className={'text-neutral-500'}/>
<p className={'text-sm text-neutral-400 ml-2'}>
278 MB
</p>
</div>
<p className={'text-xs text-neutral-600 text-center mt-1'}>of 16 GB</p>
</div>
</div>
</Link>
<div className={'grey-row-box cursor-pointer mt-2'}>
<div className={'icon'}>
<FontAwesomeIcon icon={faServer}/>
</div>
<div className={'w-1/2 ml-4'}>
<p className={'text-lg'}>My Factions Server</p>
<p className={'text-neutral-400 text-xs mt-1'}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
et dolore magna aliqua.
</p>
</div>
<div className={'flex flex-1 items-baseline justify-around'}>
<div className={'flex ml-4'}>
<FontAwesomeIcon icon={faEthernet} className={'text-neutral-500'}/>
<p className={'text-sm text-neutral-400 ml-2'}>
192.168.202.10:34556
</p>
</div>
<div className={'flex ml-4'}>
<FontAwesomeIcon icon={faMicrochip} className={'text-red-400'}/>
<p className={'text-sm text-white ml-2'}>
98.2 %
</p>
</div>
<div className={'ml-4'}>
<div className={'flex'}>
<FontAwesomeIcon icon={faMemory} className={'text-neutral-500'}/>
<p className={'text-sm text-neutral-400 ml-2'}>
376 MB
</p>
</div>
<p className={'text-xs text-neutral-600 text-center mt-1'}>of 1024 MB</p>
</div>
<div className={'ml-4'}>
<div className={'flex'}>
<FontAwesomeIcon icon={faHdd} className={'text-neutral-500'}/>
<p className={'text-sm text-neutral-400 ml-2'}>
187 MB
</p>
</div>
<p className={'text-xs text-neutral-600 text-center mt-1'}>of 32 GB</p>
</div>
</div>
export default () => {
const [ servers, setServers ] = useState<null | Server[]>(null);
const loadServers = () => getServers().then(data => setServers(data.items));
useEffect(() => {
loadServers();
}, []);
if (servers === null) {
return <Spinner size={'large'} centered={true}/>;
}
return (
<div className={'my-10'}>
{
servers.map(server => (
<ServerRow key={server.uuid} server={server} className={'mt-2'}/>
))
}
</div>
</div>
);
);
};

View file

@ -0,0 +1,135 @@
import React, { useEffect, useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faServer } from '@fortawesome/free-solid-svg-icons/faServer';
import { faEthernet } from '@fortawesome/free-solid-svg-icons/faEthernet';
import { faMicrochip } from '@fortawesome/free-solid-svg-icons/faMicrochip';
import { faMemory } from '@fortawesome/free-solid-svg-icons/faMemory';
import { faHdd } from '@fortawesome/free-solid-svg-icons/faHdd';
import { Link } from 'react-router-dom';
import { Server } from '@/api/server/getServer';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import getServerResourceUsage, { ServerStats } from '@/api/server/getServerResourceUsage';
import { bytesToHuman } from '@/helpers';
import classNames from 'classnames';
// Determines if the current value is in an alarm threshold so we can show it in red rather
// than the more faded default style.
const isAlarmState = (current: number, limit: number): boolean => {
const limitInBytes = limit * 1000 * 1000;
return current / limitInBytes >= 0.90;
};
export default ({ server, className }: { server: Server; className: string | undefined }) => {
const [ stats, setStats ] = useState<ServerStats | null>(null);
const getStats = () => getServerResourceUsage(server.uuid).then(data => setStats(data));
useEffect(() => {
let interval: any = null;
getStats().then(() => {
interval = setInterval(() => getStats(), 20000);
});
return () => {
interval && clearInterval(interval);
};
}, []);
const alarms = { cpu: false, memory: false, disk: false };
if (stats) {
alarms.cpu = server.limits.cpu === 0 ? false : (stats.cpuUsagePercent >= (server.limits.cpu * 0.9));
alarms.memory = isAlarmState(stats.memoryUsageInBytes, server.limits.memory);
alarms.disk = server.limits.disk === 0 ? false : isAlarmState(stats.diskUsageInBytes, server.limits.disk);
}
return (
<Link to={`/server/${server.id}`} className={`grey-row-box cursor-pointer ${className}`}>
<div className={'icon'}>
<FontAwesomeIcon icon={faServer}/>
</div>
<div className={'flex-1 ml-4'}>
<p className={'text-lg'}>{server.name}</p>
</div>
<div className={'w-1/4 overflow-hidden'}>
<div className={'flex ml-4'}>
<FontAwesomeIcon icon={faEthernet} className={'text-neutral-500'}/>
<p className={'text-sm text-neutral-400 ml-2'}>
{
server.allocations.filter(alloc => alloc.default).map(allocation => (
<span key={allocation.ip + allocation.port.toString()}>{allocation.alias || allocation.ip}:{allocation.port}</span>
))
}
</p>
</div>
</div>
<div className={'w-1/3 flex items-baseline relative'}>
{!stats ?
<SpinnerOverlay size={'tiny'} visible={true} backgroundOpacity={0.25}/>
:
<React.Fragment>
<div className={'flex-1 flex ml-4 justify-center'}>
<FontAwesomeIcon
icon={faMicrochip}
className={classNames({
'text-neutral-500': !alarms.cpu,
'text-red-400': alarms.cpu,
})}
/>
<p
className={classNames('text-sm ml-2', {
'text-neutral-400': !alarms.cpu,
'text-white': alarms.cpu,
})}
>
{stats.cpuUsagePercent} %
</p>
</div>
<div className={'flex-1 ml-4'}>
<div className={'flex justify-center'}>
<FontAwesomeIcon
icon={faMemory}
className={classNames({
'text-neutral-500': !alarms.memory,
'text-red-400': alarms.memory,
})}
/>
<p
className={classNames('text-sm ml-2', {
'text-neutral-400': !alarms.memory,
'text-white': alarms.memory,
})}
>
{bytesToHuman(stats.memoryUsageInBytes)}
</p>
</div>
<p className={'text-xs text-neutral-600 text-center mt-1'}>of {bytesToHuman(server.limits.memory * 1000 * 1000)}</p>
</div>
<div className={'flex-1 ml-4'}>
<div className={'flex justify-center'}>
<FontAwesomeIcon
icon={faHdd}
className={classNames({
'text-neutral-500': !alarms.disk,
'text-red-400': alarms.disk,
})}
/>
<p
className={classNames('text-sm ml-2', {
'text-neutral-400': !alarms.disk,
'text-white': alarms.disk,
})}
>
{bytesToHuman(stats.diskUsageInBytes)}
</p>
</div>
<p className={'text-xs text-neutral-600 text-center mt-1'}>
of {bytesToHuman(server.limits.disk * 1000 * 1000)}
</p>
</div>
</React.Fragment>
}
</div>
</Link>
);
};

View file

@ -53,7 +53,7 @@ export default () => {
{
({ isSubmitting, isValid }) => (
<React.Fragment>
<SpinnerOverlay large={true} visible={isSubmitting}/>
<SpinnerOverlay size={'large'} visible={isSubmitting}/>
<Form className={'m-0'}>
<Field
id={'current_email'}

View file

@ -56,7 +56,7 @@ export default () => {
{
({ isSubmitting, isValid }) => (
<React.Fragment>
<SpinnerOverlay large={true} visible={isSubmitting}/>
<SpinnerOverlay size={'large'} visible={isSubmitting}/>
<Form className={'m-0'}>
<Field
id={'current_password'}