2020-09-26 03:32:10 +00:00
|
|
|
import React, { memo, useEffect, useRef, useState } from 'react';
|
2019-08-17 23:03:10 +00:00
|
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
2020-07-15 03:48:41 +00:00
|
|
|
import { faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons';
|
2019-08-17 23:03:10 +00:00
|
|
|
import { Link } from 'react-router-dom';
|
|
|
|
import { Server } from '@/api/server/getServer';
|
2020-10-03 23:36:31 +00:00
|
|
|
import getServerResourceUsage, { ServerPowerState, ServerStats } from '@/api/server/getServerResourceUsage';
|
2021-12-04 18:35:55 +00:00
|
|
|
import { bytesToHuman, megabytesToHuman, formatIp } from '@/helpers';
|
2021-07-22 17:15:27 +00:00
|
|
|
import tw, { styled } from 'twin.macro';
|
2020-07-03 21:50:37 +00:00
|
|
|
import GreyRowBox from '@/components/elements/GreyRowBox';
|
2020-07-12 18:15:54 +00:00
|
|
|
import Spinner from '@/components/elements/Spinner';
|
2020-09-26 03:32:10 +00:00
|
|
|
import isEqual from 'react-fast-compare';
|
2019-08-17 23:03:10 +00:00
|
|
|
|
|
|
|
// Determines if the current value is in an alarm threshold so we can show it in red rather
|
|
|
|
// than the more faded default style.
|
2020-09-26 03:32:10 +00:00
|
|
|
const isAlarmState = (current: number, limit: number): boolean => limit > 0 && (current / (limit * 1024 * 1024) >= 0.90);
|
2019-08-17 23:03:10 +00:00
|
|
|
|
2020-09-26 03:32:10 +00:00
|
|
|
const Icon = memo(styled(FontAwesomeIcon)<{ $alarm: boolean }>`
|
|
|
|
${props => props.$alarm ? tw`text-red-400` : tw`text-neutral-500`};
|
|
|
|
`, isEqual);
|
|
|
|
|
|
|
|
const IconDescription = styled.p<{ $alarm: boolean }>`
|
|
|
|
${tw`text-sm ml-2`};
|
|
|
|
${props => props.$alarm ? tw`text-white` : tw`text-neutral-400`};
|
|
|
|
`;
|
2019-08-17 23:03:10 +00:00
|
|
|
|
2020-10-03 23:36:31 +00:00
|
|
|
const StatusIndicatorBox = styled(GreyRowBox)<{ $status: ServerPowerState | undefined }>`
|
|
|
|
${tw`grid grid-cols-12 gap-4 relative`};
|
2020-12-16 16:34:47 +00:00
|
|
|
|
2020-10-03 23:36:31 +00:00
|
|
|
& .status-bar {
|
|
|
|
${tw`w-2 bg-red-500 absolute right-0 z-20 rounded-full m-1 opacity-50 transition-all duration-150`};
|
|
|
|
height: calc(100% - 0.5rem);
|
2020-12-16 16:34:47 +00:00
|
|
|
|
2020-10-03 23:36:31 +00:00
|
|
|
${({ $status }) => (!$status || $status === 'offline') ? tw`bg-red-500` : ($status === 'running' ? tw`bg-green-500` : tw`bg-yellow-500`)};
|
|
|
|
}
|
2020-12-16 16:34:47 +00:00
|
|
|
|
2020-10-03 23:36:31 +00:00
|
|
|
&:hover .status-bar {
|
|
|
|
${tw`opacity-75`};
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2020-07-12 18:15:54 +00:00
|
|
|
export default ({ server, className }: { server: Server; className?: string }) => {
|
2020-04-10 19:49:09 +00:00
|
|
|
const interval = useRef<number>(null);
|
2021-01-18 00:13:49 +00:00
|
|
|
const [ isSuspended, setIsSuspended ] = useState(server.status === 'suspended');
|
2020-10-31 19:07:20 +00:00
|
|
|
const [ stats, setStats ] = useState<ServerStats | null>(null);
|
2019-08-17 23:03:10 +00:00
|
|
|
|
2020-10-31 21:11:53 +00:00
|
|
|
const getStats = () => getServerResourceUsage(server.uuid)
|
|
|
|
.then(data => setStats(data))
|
|
|
|
.catch(error => console.error(error));
|
2019-08-17 23:03:10 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
2021-01-18 00:13:49 +00:00
|
|
|
setIsSuspended(stats?.isSuspended || server.status === 'suspended');
|
|
|
|
}, [ stats?.isSuspended, server.status ]);
|
2020-10-31 19:07:20 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
// Don't waste a HTTP request if there is nothing important to show to the user because
|
|
|
|
// the server is suspended.
|
|
|
|
if (isSuspended) return;
|
|
|
|
|
2019-08-17 23:03:10 +00:00
|
|
|
getStats().then(() => {
|
2020-04-10 19:49:09 +00:00
|
|
|
// @ts-ignore
|
2021-03-21 19:29:18 +00:00
|
|
|
interval.current = setInterval(() => getStats(), 30000);
|
2019-08-17 23:03:10 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return () => {
|
2020-04-10 19:49:09 +00:00
|
|
|
interval.current && clearInterval(interval.current);
|
2019-08-17 23:03:10 +00:00
|
|
|
};
|
2020-10-31 19:07:20 +00:00
|
|
|
}, [ isSuspended ]);
|
2019-08-17 23:03:10 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2020-07-11 18:29:04 +00:00
|
|
|
|
2020-12-16 16:34:47 +00:00
|
|
|
const diskLimit = server.limits.disk !== 0 ? megabytesToHuman(server.limits.disk) : 'Unlimited';
|
|
|
|
const memoryLimit = server.limits.memory !== 0 ? megabytesToHuman(server.limits.memory) : 'Unlimited';
|
2021-06-05 15:40:48 +00:00
|
|
|
const cpuLimit = server.limits.cpu !== 0 ? server.limits.cpu + ' %' : 'Unlimited';
|
2019-08-17 23:03:10 +00:00
|
|
|
|
|
|
|
return (
|
2020-10-03 23:36:31 +00:00
|
|
|
<StatusIndicatorBox as={Link} to={`/server/${server.id}`} className={className} $status={stats?.status}>
|
|
|
|
<div css={tw`flex items-center col-span-12 sm:col-span-5 lg:col-span-6`}>
|
2020-10-03 23:25:39 +00:00
|
|
|
<div className={'icon'} css={tw`mr-4`}>
|
|
|
|
<FontAwesomeIcon icon={faServer}/>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<p css={tw`text-lg break-words`}>{server.name}</p>
|
|
|
|
{!!server.description &&
|
2020-10-03 23:36:31 +00:00
|
|
|
<p css={tw`text-sm text-neutral-300 break-words`}>{server.description}</p>
|
2020-10-03 23:25:39 +00:00
|
|
|
}
|
2019-08-17 23:03:10 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
2020-10-03 23:25:39 +00:00
|
|
|
<div css={tw`hidden lg:col-span-2 lg:flex ml-4 justify-end h-full`}>
|
|
|
|
<FontAwesomeIcon icon={faEthernet} css={tw`text-neutral-500`}/>
|
|
|
|
<p css={tw`text-sm text-neutral-400 ml-2`}>
|
|
|
|
{
|
|
|
|
server.allocations.filter(alloc => alloc.isDefault).map(allocation => (
|
|
|
|
<React.Fragment key={allocation.ip + allocation.port.toString()}>
|
2021-12-04 18:35:55 +00:00
|
|
|
{allocation.alias || formatIp(allocation.ip)}:{allocation.port}
|
2020-10-03 23:25:39 +00:00
|
|
|
</React.Fragment>
|
|
|
|
))
|
|
|
|
}
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
<div css={tw`hidden col-span-7 lg:col-span-4 sm:flex items-baseline justify-center`}>
|
2020-10-31 19:07:20 +00:00
|
|
|
{(!stats || isSuspended) ?
|
|
|
|
isSuspended ?
|
|
|
|
<div css={tw`flex-1 text-center`}>
|
2020-10-31 19:09:59 +00:00
|
|
|
<span css={tw`bg-red-500 rounded px-2 py-1 text-red-100 text-xs`}>
|
2021-01-18 00:13:49 +00:00
|
|
|
{server.status === 'suspended' ? 'Suspended' : 'Connection Error'}
|
2020-10-31 19:09:59 +00:00
|
|
|
</span>
|
2020-10-31 19:07:20 +00:00
|
|
|
</div>
|
2020-04-17 19:03:16 +00:00
|
|
|
:
|
2021-01-30 21:28:31 +00:00
|
|
|
(server.isTransferring || server.status) ?
|
2020-07-03 21:50:37 +00:00
|
|
|
<div css={tw`flex-1 text-center`}>
|
|
|
|
<span css={tw`bg-neutral-500 rounded px-2 py-1 text-neutral-100 text-xs`}>
|
2021-01-30 21:28:31 +00:00
|
|
|
{server.isTransferring ?
|
|
|
|
'Transferring'
|
|
|
|
:
|
|
|
|
server.status === 'installing' ? 'Installing' : (
|
|
|
|
server.status === 'restoring_backup' ?
|
|
|
|
'Restoring Backup'
|
|
|
|
:
|
|
|
|
'Unavailable'
|
|
|
|
)
|
|
|
|
}
|
2020-04-26 00:58:09 +00:00
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
:
|
2021-01-30 21:28:31 +00:00
|
|
|
<Spinner size={'small'}/>
|
2019-08-17 23:03:10 +00:00
|
|
|
:
|
|
|
|
<React.Fragment>
|
2021-06-05 15:40:48 +00:00
|
|
|
<div css={tw`flex-1 ml-4 sm:block hidden`}>
|
2021-06-06 19:54:04 +00:00
|
|
|
<div css={tw`flex justify-center`}>
|
|
|
|
<Icon icon={faMicrochip} $alarm={alarms.cpu}/>
|
|
|
|
<IconDescription $alarm={alarms.cpu}>
|
|
|
|
{stats.cpuUsagePercent.toFixed(2)} %
|
|
|
|
</IconDescription>
|
|
|
|
</div>
|
|
|
|
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {cpuLimit}</p>
|
2019-08-17 23:03:10 +00:00
|
|
|
</div>
|
2020-09-13 17:02:25 +00:00
|
|
|
<div css={tw`flex-1 ml-4 sm:block hidden`}>
|
2020-07-03 21:50:37 +00:00
|
|
|
<div css={tw`flex justify-center`}>
|
2020-09-26 03:32:10 +00:00
|
|
|
<Icon icon={faMemory} $alarm={alarms.memory}/>
|
|
|
|
<IconDescription $alarm={alarms.memory}>
|
2019-08-17 23:03:10 +00:00
|
|
|
{bytesToHuman(stats.memoryUsageInBytes)}
|
2020-09-26 03:32:10 +00:00
|
|
|
</IconDescription>
|
2019-08-17 23:03:10 +00:00
|
|
|
</div>
|
2020-12-16 16:34:47 +00:00
|
|
|
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {memoryLimit}</p>
|
2019-08-17 23:03:10 +00:00
|
|
|
</div>
|
2020-09-13 17:02:25 +00:00
|
|
|
<div css={tw`flex-1 ml-4 sm:block hidden`}>
|
2020-07-03 21:50:37 +00:00
|
|
|
<div css={tw`flex justify-center`}>
|
2020-09-26 03:32:10 +00:00
|
|
|
<Icon icon={faHdd} $alarm={alarms.disk}/>
|
|
|
|
<IconDescription $alarm={alarms.disk}>
|
2019-08-17 23:03:10 +00:00
|
|
|
{bytesToHuman(stats.diskUsageInBytes)}
|
2020-09-26 03:32:10 +00:00
|
|
|
</IconDescription>
|
2019-08-17 23:03:10 +00:00
|
|
|
</div>
|
2020-12-16 16:34:47 +00:00
|
|
|
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {diskLimit}</p>
|
2019-08-17 23:03:10 +00:00
|
|
|
</div>
|
|
|
|
</React.Fragment>
|
|
|
|
}
|
|
|
|
</div>
|
2020-10-03 23:36:31 +00:00
|
|
|
<div className={'status-bar'}/>
|
|
|
|
</StatusIndicatorBox>
|
2019-08-17 23:03:10 +00:00
|
|
|
);
|
|
|
|
};
|