Apply new eslint rules; default to prettier for styling
This commit is contained in:
parent
f22cce8881
commit
dc84af9937
218 changed files with 3876 additions and 3564 deletions
|
@ -11,17 +11,9 @@ interface ChartBlockProps {
|
|||
export default ({ title, legend, children }: ChartBlockProps) => (
|
||||
<div className={classNames(styles.chart_container, 'group')}>
|
||||
<div className={'flex items-center justify-between px-4 py-2'}>
|
||||
<h3 className={'font-header transition-colors duration-100 group-hover:text-gray-50'}>
|
||||
{title}
|
||||
</h3>
|
||||
{legend &&
|
||||
<p className={'text-sm flex items-center'}>
|
||||
{legend}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<div className={'z-10 ml-2'}>
|
||||
{children}
|
||||
<h3 className={'font-header transition-colors duration-100 group-hover:text-gray-50'}>{title}</h3>
|
||||
{legend && <p className={'text-sm flex items-center'}>{legend}</p>}
|
||||
</div>
|
||||
<div className={'z-10 ml-2'}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -59,16 +59,15 @@ export default () => {
|
|||
const searchBar = new SearchBarAddon({ searchAddon });
|
||||
const webLinksAddon = new WebLinksAddon();
|
||||
const scrollDownHelperAddon = new ScrollDownHelperAddon();
|
||||
const { connected, instance } = ServerContext.useStoreState(state => state.socket);
|
||||
const [ canSendCommands ] = usePermissions([ 'control.console' ]);
|
||||
const serverId = ServerContext.useStoreState(state => state.server.data!.id);
|
||||
const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring);
|
||||
const [ history, setHistory ] = usePersistedState<string[]>(`${serverId}:command_history`, []);
|
||||
const [ historyIndex, setHistoryIndex ] = useState(-1);
|
||||
const { connected, instance } = ServerContext.useStoreState((state) => state.socket);
|
||||
const [canSendCommands] = usePermissions(['control.console']);
|
||||
const serverId = ServerContext.useStoreState((state) => state.server.data!.id);
|
||||
const isTransferring = ServerContext.useStoreState((state) => state.server.data!.isTransferring);
|
||||
const [history, setHistory] = usePersistedState<string[]>(`${serverId}:command_history`, []);
|
||||
const [historyIndex, setHistoryIndex] = useState(-1);
|
||||
|
||||
const handleConsoleOutput = (line: string, prelude = false) => terminal.writeln(
|
||||
(prelude ? TERMINAL_PRELUDE : '') + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m',
|
||||
);
|
||||
const handleConsoleOutput = (line: string, prelude = false) =>
|
||||
terminal.writeln((prelude ? TERMINAL_PRELUDE : '') + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m');
|
||||
|
||||
const handleTransferStatus = (status: string) => {
|
||||
switch (status) {
|
||||
|
@ -79,17 +78,20 @@ export default () => {
|
|||
|
||||
// Sent by the source node whenever the server was archived successfully.
|
||||
case 'archive':
|
||||
terminal.writeln(TERMINAL_PRELUDE + 'Server has been archived successfully, attempting connection to target node..\u001b[0m');
|
||||
terminal.writeln(
|
||||
TERMINAL_PRELUDE +
|
||||
'Server has been archived successfully, attempting connection to target node..\u001b[0m'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDaemonErrorOutput = (line: string) => terminal.writeln(
|
||||
TERMINAL_PRELUDE + '\u001b[1m\u001b[41m' + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m',
|
||||
);
|
||||
const handleDaemonErrorOutput = (line: string) =>
|
||||
terminal.writeln(
|
||||
TERMINAL_PRELUDE + '\u001b[1m\u001b[41m' + line.replace(/(?:\r\n|\r|\n)$/im, '') + '\u001b[0m'
|
||||
);
|
||||
|
||||
const handlePowerChangeEvent = (state: string) => terminal.writeln(
|
||||
TERMINAL_PRELUDE + 'Server marked as ' + state + '...\u001b[0m',
|
||||
);
|
||||
const handlePowerChangeEvent = (state: string) =>
|
||||
terminal.writeln(TERMINAL_PRELUDE + 'Server marked as ' + state + '...\u001b[0m');
|
||||
|
||||
const handleCommandKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'ArrowUp') {
|
||||
|
@ -112,7 +114,7 @@ export default () => {
|
|||
|
||||
const command = e.currentTarget.value;
|
||||
if (e.key === 'Enter' && command.length > 0) {
|
||||
setHistory(prevHistory => [ command, ...prevHistory! ].slice(0, 32));
|
||||
setHistory((prevHistory) => [command, ...prevHistory!].slice(0, 32));
|
||||
setHistoryIndex(-1);
|
||||
|
||||
instance && instance.send('send command', command);
|
||||
|
@ -146,13 +148,16 @@ export default () => {
|
|||
return true;
|
||||
});
|
||||
}
|
||||
}, [ terminal, connected ]);
|
||||
}, [terminal, connected]);
|
||||
|
||||
useEventListener('resize', debounce(() => {
|
||||
if (terminal.element) {
|
||||
fitAddon.fit();
|
||||
}
|
||||
}, 100));
|
||||
useEventListener(
|
||||
'resize',
|
||||
debounce(() => {
|
||||
if (terminal.element) {
|
||||
fitAddon.fit();
|
||||
}
|
||||
}, 100)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const listeners: Record<string, (s: string) => void> = {
|
||||
|
@ -161,7 +166,7 @@ export default () => {
|
|||
[SocketEvent.INSTALL_OUTPUT]: handleConsoleOutput,
|
||||
[SocketEvent.TRANSFER_LOGS]: handleConsoleOutput,
|
||||
[SocketEvent.TRANSFER_STATUS]: handleTransferStatus,
|
||||
[SocketEvent.DAEMON_MESSAGE]: line => handleConsoleOutput(line, true),
|
||||
[SocketEvent.DAEMON_MESSAGE]: (line) => handleConsoleOutput(line, true),
|
||||
[SocketEvent.DAEMON_ERROR]: handleDaemonErrorOutput,
|
||||
};
|
||||
|
||||
|
@ -184,15 +189,17 @@ export default () => {
|
|||
});
|
||||
}
|
||||
};
|
||||
}, [ connected, instance ]);
|
||||
}, [connected, instance]);
|
||||
|
||||
return (
|
||||
<div className={styles.terminal}>
|
||||
<SpinnerOverlay visible={!connected} size={'large'}/>
|
||||
<div className={classNames(styles.container, styles.overflows_container, { 'rounded-b': !canSendCommands })}>
|
||||
<div id={styles.terminal} ref={ref}/>
|
||||
<SpinnerOverlay visible={!connected} size={'large'} />
|
||||
<div
|
||||
className={classNames(styles.container, styles.overflows_container, { 'rounded-b': !canSendCommands })}
|
||||
>
|
||||
<div id={styles.terminal} ref={ref} />
|
||||
</div>
|
||||
{canSendCommands &&
|
||||
{canSendCommands && (
|
||||
<div className={classNames('relative', styles.overflows_container)}>
|
||||
<input
|
||||
className={classNames('peer', styles.command_input)}
|
||||
|
@ -204,11 +211,16 @@ export default () => {
|
|||
autoCorrect={'off'}
|
||||
autoCapitalize={'none'}
|
||||
/>
|
||||
<div className={classNames('text-gray-100 peer-focus:text-gray-50 peer-focus:animate-pulse', styles.command_icon)}>
|
||||
<ChevronDoubleRightIcon className={'w-4 h-4'}/>
|
||||
<div
|
||||
className={classNames(
|
||||
'text-gray-100 peer-focus:text-gray-50 peer-focus:animate-pulse',
|
||||
styles.command_icon
|
||||
)}
|
||||
>
|
||||
<ChevronDoubleRightIcon className={'w-4 h-4'} />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,12 +10,15 @@ interface PowerButtonProps {
|
|||
}
|
||||
|
||||
export default ({ className }: PowerButtonProps) => {
|
||||
const [ open, setOpen ] = useState(false);
|
||||
const status = ServerContext.useStoreState(state => state.status.value);
|
||||
const instance = ServerContext.useStoreState(state => state.socket.instance);
|
||||
const [open, setOpen] = useState(false);
|
||||
const status = ServerContext.useStoreState((state) => state.status.value);
|
||||
const instance = ServerContext.useStoreState((state) => state.socket.instance);
|
||||
|
||||
const killable = status === 'stopping';
|
||||
const onButtonClick = (action: PowerAction | 'kill-confirmed', e: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
||||
const onButtonClick = (
|
||||
action: PowerAction | 'kill-confirmed',
|
||||
e: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||
): void => {
|
||||
e.preventDefault();
|
||||
if (action === 'kill') {
|
||||
return setOpen(true);
|
||||
|
@ -31,7 +34,7 @@ export default ({ className }: PowerButtonProps) => {
|
|||
if (status === 'offline') {
|
||||
setOpen(false);
|
||||
}
|
||||
}, [ status ]);
|
||||
}, [status]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
|
|
|
@ -15,11 +15,11 @@ import ServerDetailsBlock from '@/components/server/console/ServerDetailsBlock';
|
|||
export type PowerAction = 'start' | 'stop' | 'restart' | 'kill';
|
||||
|
||||
const ServerConsoleContainer = () => {
|
||||
const name = ServerContext.useStoreState(state => state.server.data!.name);
|
||||
const description = ServerContext.useStoreState(state => state.server.data!.description);
|
||||
const isInstalling = ServerContext.useStoreState(state => state.server.data!.isInstalling);
|
||||
const isTransferring = ServerContext.useStoreState(state => state.server.data!.isTransferring);
|
||||
const eggFeatures = ServerContext.useStoreState(state => state.server.data!.eggFeatures, isEqual);
|
||||
const name = ServerContext.useStoreState((state) => state.server.data!.name);
|
||||
const description = ServerContext.useStoreState((state) => state.server.data!.description);
|
||||
const isInstalling = ServerContext.useStoreState((state) => state.server.data!.isInstalling);
|
||||
const isTransferring = ServerContext.useStoreState((state) => state.server.data!.isTransferring);
|
||||
const eggFeatures = ServerContext.useStoreState((state) => state.server.data!.eggFeatures, isEqual);
|
||||
|
||||
return (
|
||||
<ServerContentBlock title={'Console'} className={'flex flex-col gap-2 sm:gap-4'}>
|
||||
|
@ -29,19 +29,19 @@ const ServerConsoleContainer = () => {
|
|||
<p className={'text-sm line-clamp-2'}>{description}</p>
|
||||
</div>
|
||||
<div className={'flex-1'}>
|
||||
<Can action={[ 'control.start', 'control.stop', 'control.restart' ]} matchAny>
|
||||
<PowerButtons className={'flex sm:justify-end space-x-2'}/>
|
||||
<Can action={['control.start', 'control.stop', 'control.restart']} matchAny>
|
||||
<PowerButtons className={'flex sm:justify-end space-x-2'} />
|
||||
</Can>
|
||||
</div>
|
||||
</div>
|
||||
<div className={'grid grid-cols-4 gap-2 sm:gap-4'}>
|
||||
<ServerDetailsBlock className={'col-span-4 lg:col-span-1 order-last lg:order-none'}/>
|
||||
<ServerDetailsBlock className={'col-span-4 lg:col-span-1 order-last lg:order-none'} />
|
||||
<div className={'col-span-4 lg:col-span-3'}>
|
||||
<Spinner.Suspense>
|
||||
<Console/>
|
||||
<Console />
|
||||
</Spinner.Suspense>
|
||||
</div>
|
||||
{isInstalling ?
|
||||
{isInstalling ? (
|
||||
<div css={tw`mt-4 rounded bg-yellow-500 p-3`}>
|
||||
<ContentContainer>
|
||||
<p css={tw`text-sm text-yellow-900`}>
|
||||
|
@ -50,26 +50,23 @@ const ServerConsoleContainer = () => {
|
|||
</p>
|
||||
</ContentContainer>
|
||||
</div>
|
||||
:
|
||||
isTransferring ?
|
||||
<div css={tw`mt-4 rounded bg-yellow-500 p-3`}>
|
||||
<ContentContainer>
|
||||
<p css={tw`text-sm text-yellow-900`}>
|
||||
This server is currently being transferred to another node and all actions
|
||||
are unavailable.
|
||||
</p>
|
||||
</ContentContainer>
|
||||
</div>
|
||||
:
|
||||
null
|
||||
}
|
||||
) : isTransferring ? (
|
||||
<div css={tw`mt-4 rounded bg-yellow-500 p-3`}>
|
||||
<ContentContainer>
|
||||
<p css={tw`text-sm text-yellow-900`}>
|
||||
This server is currently being transferred to another node and all actions are
|
||||
unavailable.
|
||||
</p>
|
||||
</ContentContainer>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={'grid grid-cols-1 md:grid-cols-3 gap-2 sm:gap-4'}>
|
||||
<Spinner.Suspense>
|
||||
<StatGraphs/>
|
||||
<StatGraphs />
|
||||
</Spinner.Suspense>
|
||||
</div>
|
||||
<Features enabled={eggFeatures}/>
|
||||
<Features enabled={eggFeatures} />
|
||||
</ServerContentBlock>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@ import classNames from 'classnames';
|
|||
type Stats = Record<'memory' | 'cpu' | 'disk' | 'uptime' | 'rx' | 'tx', number>;
|
||||
|
||||
const getBackgroundColor = (value: number, max: number | null): string | undefined => {
|
||||
const delta = !max ? 0 : (value / max);
|
||||
const delta = !max ? 0 : value / max;
|
||||
|
||||
if (delta > 0.8) {
|
||||
if (delta > 0.9) {
|
||||
|
@ -32,14 +32,14 @@ const getBackgroundColor = (value: number, max: number | null): string | undefin
|
|||
};
|
||||
|
||||
const ServerDetailsBlock = ({ className }: { className?: string }) => {
|
||||
const [ stats, setStats ] = useState<Stats>({ memory: 0, cpu: 0, disk: 0, uptime: 0, tx: 0, rx: 0 });
|
||||
const [stats, setStats] = useState<Stats>({ memory: 0, cpu: 0, disk: 0, uptime: 0, tx: 0, rx: 0 });
|
||||
|
||||
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 limits = ServerContext.useStoreState(state => state.server.data!.limits);
|
||||
const allocation = ServerContext.useStoreState(state => {
|
||||
const match = state.server.data!.allocations.find(allocation => allocation.isDefault);
|
||||
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 limits = ServerContext.useStoreState((state) => state.server.data!.limits);
|
||||
const allocation = ServerContext.useStoreState((state) => {
|
||||
const match = state.server.data!.allocations.find((allocation) => allocation.isDefault);
|
||||
|
||||
return !match ? 'n/a' : `${match.alias || ip(match.ip)}:${match.port}`;
|
||||
});
|
||||
|
@ -50,7 +50,7 @@ const ServerDetailsBlock = ({ className }: { className?: string }) => {
|
|||
}
|
||||
|
||||
instance.send(SocketRequest.SEND_STATS);
|
||||
}, [ instance, connected ]);
|
||||
}, [instance, connected]);
|
||||
|
||||
useWebsocketEvent(SocketEvent.STATS, (data) => {
|
||||
let stats: any = {};
|
||||
|
@ -78,51 +78,42 @@ const ServerDetailsBlock = ({ className }: { className?: string }) => {
|
|||
<StatBlock
|
||||
icon={faClock}
|
||||
title={'Uptime'}
|
||||
color={getBackgroundColor(status === 'running' ? 0 : (status !== 'offline' ? 9 : 10), 10)}
|
||||
color={getBackgroundColor(status === 'running' ? 0 : status !== 'offline' ? 9 : 10, 10)}
|
||||
>
|
||||
{stats.uptime > 0 ?
|
||||
<UptimeDuration uptime={stats.uptime / 1000}/>
|
||||
:
|
||||
'Offline'
|
||||
}
|
||||
{stats.uptime > 0 ? <UptimeDuration uptime={stats.uptime / 1000} /> : 'Offline'}
|
||||
</StatBlock>
|
||||
<StatBlock
|
||||
icon={faMicrochip}
|
||||
title={'CPU Load'}
|
||||
color={getBackgroundColor(stats.cpu, limits.cpu)}
|
||||
description={limits.cpu
|
||||
? `This server is allowed to use up to ${limits.cpu}% of the host's available CPU resources.`
|
||||
: 'No CPU limit has been configured for this server.'
|
||||
description={
|
||||
limits.cpu
|
||||
? `This server is allowed to use up to ${limits.cpu}% of the host's available CPU resources.`
|
||||
: 'No CPU limit has been configured for this server.'
|
||||
}
|
||||
>
|
||||
{status === 'offline' ?
|
||||
<span className={'text-gray-400'}>Offline</span>
|
||||
:
|
||||
`${stats.cpu.toFixed(2)}%`
|
||||
}
|
||||
{status === 'offline' ? <span className={'text-gray-400'}>Offline</span> : `${stats.cpu.toFixed(2)}%`}
|
||||
</StatBlock>
|
||||
<StatBlock
|
||||
icon={faMemory}
|
||||
title={'Memory'}
|
||||
color={getBackgroundColor(stats.memory / 1024, limits.memory * 1024)}
|
||||
description={limits.memory
|
||||
? `This server is allowed to use up to ${bytesToString(mbToBytes(limits.memory))} of memory.`
|
||||
: 'No memory limit has been configured for this server.'
|
||||
description={
|
||||
limits.memory
|
||||
? `This server is allowed to use up to ${bytesToString(mbToBytes(limits.memory))} of memory.`
|
||||
: 'No memory limit has been configured for this server.'
|
||||
}
|
||||
>
|
||||
{status === 'offline' ?
|
||||
<span className={'text-gray-400'}>Offline</span>
|
||||
:
|
||||
bytesToString(stats.memory)
|
||||
}
|
||||
{status === 'offline' ? <span className={'text-gray-400'}>Offline</span> : bytesToString(stats.memory)}
|
||||
</StatBlock>
|
||||
<StatBlock
|
||||
icon={faHdd}
|
||||
title={'Disk'}
|
||||
color={getBackgroundColor(stats.disk / 1024, limits.disk * 1024)}
|
||||
description={limits.disk
|
||||
? `This server is allowed to use up to ${bytesToString(mbToBytes(limits.disk))} of disk space.`
|
||||
: 'No disk space limit has been configured for this server.'
|
||||
description={
|
||||
limits.disk
|
||||
? `This server is allowed to use up to ${bytesToString(mbToBytes(limits.disk))} of disk space.`
|
||||
: 'No disk space limit has been configured for this server.'
|
||||
}
|
||||
>
|
||||
{bytesToString(stats.disk)}
|
||||
|
@ -132,22 +123,16 @@ const ServerDetailsBlock = ({ className }: { className?: string }) => {
|
|||
title={'Network (Inbound)'}
|
||||
description={'The total amount of network traffic that your server has recieved since it was started.'}
|
||||
>
|
||||
{status === 'offline' ?
|
||||
<span className={'text-gray-400'}>Offline</span>
|
||||
:
|
||||
bytesToString(stats.tx)
|
||||
}
|
||||
{status === 'offline' ? <span className={'text-gray-400'}>Offline</span> : bytesToString(stats.tx)}
|
||||
</StatBlock>
|
||||
<StatBlock
|
||||
icon={faCloudUploadAlt}
|
||||
title={'Network (Outbound)'}
|
||||
description={'The total amount of traffic your server has sent across the internet since it was started.'}
|
||||
>
|
||||
{status === 'offline' ?
|
||||
<span className={'text-gray-400'}>Offline</span>
|
||||
:
|
||||
bytesToString(stats.rx)
|
||||
description={
|
||||
'The total amount of traffic your server has sent across the internet since it was started.'
|
||||
}
|
||||
>
|
||||
{status === 'offline' ? <span className={'text-gray-400'}>Offline</span> : bytesToString(stats.rx)}
|
||||
</StatBlock>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -21,7 +21,7 @@ export default ({ title, icon, color, description, className, children }: StatBl
|
|||
return (
|
||||
<Tooltip arrow placement={'top'} disabled={!description} content={description || ''}>
|
||||
<div className={classNames(styles.stat_block, 'bg-gray-600', className)}>
|
||||
<div className={classNames(styles.status_bar, color || 'bg-gray-700')}/>
|
||||
<div className={classNames(styles.status_bar, color || 'bg-gray-700')} />
|
||||
<div className={classNames(styles.icon, color || 'bg-gray-700')}>
|
||||
<Icon
|
||||
icon={icon}
|
||||
|
|
|
@ -12,8 +12,8 @@ import ChartBlock from '@/components/server/console/ChartBlock';
|
|||
import Tooltip from '@/components/elements/tooltip/Tooltip';
|
||||
|
||||
export default () => {
|
||||
const status = ServerContext.useStoreState(state => state.status.value);
|
||||
const limits = ServerContext.useStoreState(state => state.server.data!.limits);
|
||||
const status = ServerContext.useStoreState((state) => state.status.value);
|
||||
const limits = ServerContext.useStoreState((state) => state.server.data!.limits);
|
||||
const previous = useRef<Record<'tx' | 'rx', number>>({ tx: -1, rx: -1 });
|
||||
|
||||
const cpu = useChartTickLabel('CPU', limits.cpu, '%');
|
||||
|
@ -24,14 +24,14 @@ export default () => {
|
|||
scales: {
|
||||
y: {
|
||||
ticks: {
|
||||
callback (value) {
|
||||
callback(value) {
|
||||
return bytesToString(typeof value === 'string' ? parseInt(value, 10) : value);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
callback (opts, index) {
|
||||
callback(opts, index) {
|
||||
return {
|
||||
...opts,
|
||||
label: !index ? 'Network In' : 'Network Out',
|
||||
|
@ -47,7 +47,7 @@ export default () => {
|
|||
memory.clear();
|
||||
network.clear();
|
||||
}
|
||||
}, [ status ]);
|
||||
}, [status]);
|
||||
|
||||
useWebsocketEvent(SocketEvent.STATS, (data: string) => {
|
||||
let values: any = {};
|
||||
|
@ -70,25 +70,25 @@ export default () => {
|
|||
return (
|
||||
<>
|
||||
<ChartBlock title={'CPU Load'}>
|
||||
<Line {...cpu.props}/>
|
||||
<Line {...cpu.props} />
|
||||
</ChartBlock>
|
||||
<ChartBlock title={'Memory'}>
|
||||
<Line {...memory.props}/>
|
||||
<Line {...memory.props} />
|
||||
</ChartBlock>
|
||||
<ChartBlock
|
||||
title={'Network'}
|
||||
legend={
|
||||
<>
|
||||
<Tooltip arrow content={'Inbound'}>
|
||||
<CloudDownloadIcon className={'mr-2 w-4 h-4 text-yellow-400'}/>
|
||||
<CloudDownloadIcon className={'mr-2 w-4 h-4 text-yellow-400'} />
|
||||
</Tooltip>
|
||||
<Tooltip arrow content={'Outbound'}>
|
||||
<CloudUploadIcon className={'w-4 h-4 text-cyan-400'}/>
|
||||
<CloudUploadIcon className={'w-4 h-4 text-cyan-400'} />
|
||||
</Tooltip>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Line {...network.props}/>
|
||||
<Line {...network.props} />
|
||||
</ChartBlock>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -70,24 +70,33 @@ const options: ChartOptions<'line'> = {
|
|||
},
|
||||
};
|
||||
|
||||
function getOptions (opts?: DeepPartial<ChartOptions<'line'>> | undefined): ChartOptions<'line'> {
|
||||
function getOptions(opts?: DeepPartial<ChartOptions<'line'>> | undefined): ChartOptions<'line'> {
|
||||
return deepmerge(options, opts || {});
|
||||
}
|
||||
|
||||
type ChartDatasetCallback = (value: ChartDataset<'line'>, index: number) => ChartDataset<'line'>;
|
||||
|
||||
function getEmptyData (label: string, sets = 1, callback?: ChartDatasetCallback | undefined): ChartData<'line'> {
|
||||
const next = callback || (value => value);
|
||||
function getEmptyData(label: string, sets = 1, callback?: ChartDatasetCallback | undefined): ChartData<'line'> {
|
||||
const next = callback || ((value) => value);
|
||||
|
||||
return {
|
||||
labels: Array(20).fill(0).map((_, index) => index),
|
||||
datasets: Array(sets).fill(0).map((_, index) => next({
|
||||
fill: true,
|
||||
label,
|
||||
data: Array(20).fill(0),
|
||||
borderColor: theme('colors.cyan.400'),
|
||||
backgroundColor: hexToRgba(theme('colors.cyan.700'), 0.5),
|
||||
}, index)),
|
||||
labels: Array(20)
|
||||
.fill(0)
|
||||
.map((_, index) => index),
|
||||
datasets: Array(sets)
|
||||
.fill(0)
|
||||
.map((_, index) =>
|
||||
next(
|
||||
{
|
||||
fill: true,
|
||||
label,
|
||||
data: Array(20).fill(0),
|
||||
borderColor: theme('colors.cyan.400'),
|
||||
backgroundColor: hexToRgba(theme('colors.cyan.700'), 0.5),
|
||||
},
|
||||
index
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -99,28 +108,36 @@ interface UseChartOptions {
|
|||
callback?: ChartDatasetCallback | undefined;
|
||||
}
|
||||
|
||||
function useChart (label: string, opts?: UseChartOptions) {
|
||||
const options = getOptions(typeof opts?.options === 'number' ? { scales: { y: { min: 0, suggestedMax: opts.options } } } : opts?.options);
|
||||
const [ data, setData ] = useState(getEmptyData(label, opts?.sets || 1, opts?.callback));
|
||||
function useChart(label: string, opts?: UseChartOptions) {
|
||||
const options = getOptions(
|
||||
typeof opts?.options === 'number' ? { scales: { y: { min: 0, suggestedMax: opts.options } } } : opts?.options
|
||||
);
|
||||
const [data, setData] = useState(getEmptyData(label, opts?.sets || 1, opts?.callback));
|
||||
|
||||
const push = (items: number | null | ((number | null)[])) => setData(state => merge(state, {
|
||||
datasets: (Array.isArray(items) ? items : [ items ]).map((item, index) => ({
|
||||
...state.datasets[index],
|
||||
data: state.datasets[index].data.slice(1).concat(item),
|
||||
})),
|
||||
}));
|
||||
const push = (items: number | null | (number | null)[]) =>
|
||||
setData((state) =>
|
||||
merge(state, {
|
||||
datasets: (Array.isArray(items) ? items : [items]).map((item, index) => ({
|
||||
...state.datasets[index],
|
||||
data: state.datasets[index].data.slice(1).concat(item),
|
||||
})),
|
||||
})
|
||||
);
|
||||
|
||||
const clear = () => setData(state => merge(state, {
|
||||
datasets: state.datasets.map(value => ({
|
||||
...value,
|
||||
data: Array(20).fill(0),
|
||||
})),
|
||||
}));
|
||||
const clear = () =>
|
||||
setData((state) =>
|
||||
merge(state, {
|
||||
datasets: state.datasets.map((value) => ({
|
||||
...value,
|
||||
data: Array(20).fill(0),
|
||||
})),
|
||||
})
|
||||
);
|
||||
|
||||
return { props: { data, options }, push, clear };
|
||||
}
|
||||
|
||||
function useChartTickLabel (label: string, max: number, tickLabel: string) {
|
||||
function useChartTickLabel(label: string, max: number, tickLabel: string) {
|
||||
return useChart(label, {
|
||||
sets: 1,
|
||||
options: {
|
||||
|
@ -128,7 +145,7 @@ function useChartTickLabel (label: string, max: number, tickLabel: string) {
|
|||
y: {
|
||||
suggestedMax: max,
|
||||
ticks: {
|
||||
callback (value) {
|
||||
callback(value) {
|
||||
return `${value}${tickLabel}`;
|
||||
},
|
||||
},
|
||||
|
|
Reference in a new issue