Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/1.0-develop/resources/scripts/components/dashboard/ServerRow.tsx
7461 views
1
import React, { memo, useEffect, useRef, useState } from 'react';
2
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3
import { faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons';
4
import { Link } from 'react-router-dom';
5
import { Server } from '@/api/server/getServer';
6
import getServerResourceUsage, { ServerPowerState, ServerStats } from '@/api/server/getServerResourceUsage';
7
import { bytesToString, ip, mbToBytes } from '@/lib/formatters';
8
import tw from 'twin.macro';
9
import GreyRowBox from '@/components/elements/GreyRowBox';
10
import Spinner from '@/components/elements/Spinner';
11
import styled from 'styled-components/macro';
12
import isEqual from 'react-fast-compare';
13
14
// Determines if the current value is in an alarm threshold so we can show it in red rather
15
// than the more faded default style.
16
const isAlarmState = (current: number, limit: number): boolean => limit > 0 && current / (limit * 1024 * 1024) >= 0.9;
17
18
const Icon = memo(
19
styled(FontAwesomeIcon)<{ $alarm: boolean }>`
20
${(props) => (props.$alarm ? tw`text-red-400` : tw`text-neutral-500`)};
21
`,
22
isEqual
23
);
24
25
const IconDescription = styled.p<{ $alarm: boolean }>`
26
${tw`text-sm ml-2`};
27
${(props) => (props.$alarm ? tw`text-white` : tw`text-neutral-400`)};
28
`;
29
30
const StatusIndicatorBox = styled(GreyRowBox)<{ $status: ServerPowerState | undefined }>`
31
${tw`grid grid-cols-12 gap-4 relative`};
32
33
& .status-bar {
34
${tw`w-2 bg-red-500 absolute right-0 z-20 rounded-full m-1 opacity-50 transition-all duration-150`};
35
height: calc(100% - 0.5rem);
36
37
${({ $status }) =>
38
!$status || $status === 'offline'
39
? tw`bg-red-500`
40
: $status === 'running'
41
? tw`bg-green-500`
42
: tw`bg-yellow-500`};
43
}
44
45
&:hover .status-bar {
46
${tw`opacity-75`};
47
}
48
`;
49
50
type Timer = ReturnType<typeof setInterval>;
51
52
export default ({ server, className }: { server: Server; className?: string }) => {
53
const interval = useRef<Timer>(null) as React.MutableRefObject<Timer>;
54
const [isSuspended, setIsSuspended] = useState(server.status === 'suspended');
55
const [stats, setStats] = useState<ServerStats | null>(null);
56
57
const getStats = () =>
58
getServerResourceUsage(server.uuid)
59
.then((data) => setStats(data))
60
.catch((error) => console.error(error));
61
62
useEffect(() => {
63
setIsSuspended(stats?.isSuspended || server.status === 'suspended');
64
}, [stats?.isSuspended, server.status]);
65
66
useEffect(() => {
67
// Don't waste a HTTP request if there is nothing important to show to the user because
68
// the server is suspended.
69
if (isSuspended) return;
70
71
getStats().then(() => {
72
interval.current = setInterval(() => getStats(), 30000);
73
});
74
75
return () => {
76
interval.current && clearInterval(interval.current);
77
};
78
}, [isSuspended]);
79
80
const alarms = { cpu: false, memory: false, disk: false };
81
if (stats) {
82
alarms.cpu = server.limits.cpu === 0 ? false : stats.cpuUsagePercent >= server.limits.cpu * 0.9;
83
alarms.memory = isAlarmState(stats.memoryUsageInBytes, server.limits.memory);
84
alarms.disk = server.limits.disk === 0 ? false : isAlarmState(stats.diskUsageInBytes, server.limits.disk);
85
}
86
87
const diskLimit = server.limits.disk !== 0 ? bytesToString(mbToBytes(server.limits.disk)) : 'Unlimited';
88
const memoryLimit = server.limits.memory !== 0 ? bytesToString(mbToBytes(server.limits.memory)) : 'Unlimited';
89
const cpuLimit = server.limits.cpu !== 0 ? server.limits.cpu + ' %' : 'Unlimited';
90
91
return (
92
<StatusIndicatorBox as={Link} to={`/server/${server.id}`} className={className} $status={stats?.status}>
93
<div css={tw`flex items-center col-span-12 sm:col-span-5 lg:col-span-6`}>
94
<div className={'icon mr-4'}>
95
<FontAwesomeIcon icon={faServer} />
96
</div>
97
<div>
98
<p css={tw`text-lg break-words`}>{server.name}</p>
99
{!!server.description && (
100
<p css={tw`text-sm text-neutral-300 break-words line-clamp-2`}>{server.description}</p>
101
)}
102
</div>
103
</div>
104
<div css={tw`flex-1 ml-4 lg:block lg:col-span-2 hidden`}>
105
<div css={tw`flex justify-center`}>
106
<FontAwesomeIcon icon={faEthernet} css={tw`text-neutral-500`} />
107
<p css={tw`text-sm text-neutral-400 ml-2`}>
108
{server.allocations
109
.filter((alloc) => alloc.isDefault)
110
.map((allocation) => (
111
<React.Fragment key={allocation.ip + allocation.port.toString()}>
112
{allocation.alias || ip(allocation.ip)}:{allocation.port}
113
</React.Fragment>
114
))}
115
</p>
116
</div>
117
</div>
118
<div css={tw`hidden col-span-7 lg:col-span-4 sm:flex items-baseline justify-center`}>
119
{!stats || isSuspended ? (
120
isSuspended ? (
121
<div css={tw`flex-1 text-center`}>
122
<span css={tw`bg-red-500 rounded px-2 py-1 text-red-100 text-xs`}>
123
{server.status === 'suspended' ? 'Suspended' : 'Connection Error'}
124
</span>
125
</div>
126
) : server.isTransferring || server.status ? (
127
<div css={tw`flex-1 text-center`}>
128
<span css={tw`bg-neutral-500 rounded px-2 py-1 text-neutral-100 text-xs`}>
129
{server.isTransferring
130
? 'Transferring'
131
: server.status === 'installing'
132
? 'Installing'
133
: server.status === 'restoring_backup'
134
? 'Restoring Backup'
135
: 'Unavailable'}
136
</span>
137
</div>
138
) : (
139
<Spinner size={'small'} />
140
)
141
) : (
142
<React.Fragment>
143
<div css={tw`flex-1 ml-4 sm:block hidden`}>
144
<div css={tw`flex justify-center`}>
145
<Icon icon={faMicrochip} $alarm={alarms.cpu} />
146
<IconDescription $alarm={alarms.cpu}>
147
{stats.cpuUsagePercent.toFixed(2)} %
148
</IconDescription>
149
</div>
150
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {cpuLimit}</p>
151
</div>
152
<div css={tw`flex-1 ml-4 sm:block hidden`}>
153
<div css={tw`flex justify-center`}>
154
<Icon icon={faMemory} $alarm={alarms.memory} />
155
<IconDescription $alarm={alarms.memory}>
156
{bytesToString(stats.memoryUsageInBytes)}
157
</IconDescription>
158
</div>
159
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {memoryLimit}</p>
160
</div>
161
<div css={tw`flex-1 ml-4 sm:block hidden`}>
162
<div css={tw`flex justify-center`}>
163
<Icon icon={faHdd} $alarm={alarms.disk} />
164
<IconDescription $alarm={alarms.disk}>
165
{bytesToString(stats.diskUsageInBytes)}
166
</IconDescription>
167
</div>
168
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {diskLimit}</p>
169
</div>
170
</React.Fragment>
171
)}
172
</div>
173
<div className={'status-bar'} />
174
</StatusIndicatorBox>
175
);
176
};
177
178