Some mobile improvements for the UI; make console fill space better
This commit is contained in:
parent
faff263f17
commit
54c619e6ba
|
@ -1,6 +1,7 @@
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import styled, { css, keyframes } from 'styled-components/macro';
|
import styled, { css, keyframes } from 'styled-components/macro';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
import ErrorBoundary from '@/components/elements/ErrorBoundary';
|
||||||
|
|
||||||
export type SpinnerSize = 'small' | 'base' | 'large';
|
export type SpinnerSize = 'small' | 'base' | 'large';
|
||||||
|
|
||||||
|
@ -25,12 +26,12 @@ const SpinnerComponent = styled.div<Props>`
|
||||||
border-width: 3px;
|
border-width: 3px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: ${spin} 1s cubic-bezier(0.55, 0.25, 0.25, 0.70) infinite;
|
animation: ${spin} 1s cubic-bezier(0.55, 0.25, 0.25, 0.70) infinite;
|
||||||
|
|
||||||
${props => props.size === 'small' ? tw`w-4 h-4 border-2` : (props.size === 'large' ? css`
|
${props => props.size === 'small' ? tw`w-4 h-4 border-2` : (props.size === 'large' ? css`
|
||||||
${tw`w-16 h-16`};
|
${tw`w-16 h-16`};
|
||||||
border-width: 6px;
|
border-width: 6px;
|
||||||
` : null)};
|
` : null)};
|
||||||
|
|
||||||
border-color: ${props => !props.isBlue ? 'rgba(255, 255, 255, 0.2)' : 'hsla(212, 92%, 43%, 0.2)'};
|
border-color: ${props => !props.isBlue ? 'rgba(255, 255, 255, 0.2)' : 'hsla(212, 92%, 43%, 0.2)'};
|
||||||
border-top-color: ${props => !props.isBlue ? 'rgb(255, 255, 255)' : 'hsl(212, 92%, 43%)'};
|
border-top-color: ${props => !props.isBlue ? 'rgb(255, 255, 255)' : 'hsl(212, 92%, 43%)'};
|
||||||
`;
|
`;
|
||||||
|
@ -58,7 +59,9 @@ Spinner.Size = {
|
||||||
|
|
||||||
Spinner.Suspense = ({ children, centered = true, size = Spinner.Size.LARGE, ...props }) => (
|
Spinner.Suspense = ({ children, centered = true, size = Spinner.Size.LARGE, ...props }) => (
|
||||||
<Suspense fallback={<Spinner centered={centered} size={size} {...props}/>}>
|
<Suspense fallback={<Spinner centered={centered} size={size} {...props}/>}>
|
||||||
{children}
|
<ErrorBoundary>
|
||||||
|
{children}
|
||||||
|
</ErrorBoundary>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
Spinner.Suspense.displayName = 'Spinner.Suspense';
|
Spinner.Suspense.displayName = 'Spinner.Suspense';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Terminal, ITerminalOptions } from 'xterm';
|
import { ITerminalOptions, Terminal } from 'xterm';
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
import { FitAddon } from 'xterm-addon-fit';
|
||||||
import { SearchAddon } from 'xterm-addon-search';
|
import { SearchAddon } from 'xterm-addon-search';
|
||||||
import { SearchBarAddon } from 'xterm-addon-search-bar';
|
import { SearchBarAddon } from 'xterm-addon-search-bar';
|
||||||
|
@ -7,14 +7,16 @@ import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||||
import { ScrollDownHelperAddon } from '@/plugins/XtermScrollDownHelperAddon';
|
import { ScrollDownHelperAddon } from '@/plugins/XtermScrollDownHelperAddon';
|
||||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import { ServerContext } from '@/state/server';
|
import { ServerContext } from '@/state/server';
|
||||||
import styled from 'styled-components/macro';
|
|
||||||
import { usePermissions } from '@/plugins/usePermissions';
|
import { usePermissions } from '@/plugins/usePermissions';
|
||||||
import tw, { theme as th } from 'twin.macro';
|
import { theme as th } from 'twin.macro';
|
||||||
import 'xterm/css/xterm.css';
|
import 'xterm/css/xterm.css';
|
||||||
import useEventListener from '@/plugins/useEventListener';
|
import useEventListener from '@/plugins/useEventListener';
|
||||||
import { debounce } from 'debounce';
|
import { debounce } from 'debounce';
|
||||||
import { usePersistedState } from '@/plugins/usePersistedState';
|
import { usePersistedState } from '@/plugins/usePersistedState';
|
||||||
import { SocketEvent, SocketRequest } from '@/components/server/events';
|
import { SocketEvent, SocketRequest } from '@/components/server/events';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import styles from './style.module.css';
|
||||||
|
import { ChevronDoubleRightIcon } from '@heroicons/react/solid';
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
background: th`colors.black`.toString(),
|
background: th`colors.black`.toString(),
|
||||||
|
@ -48,23 +50,6 @@ const terminalProps: ITerminalOptions = {
|
||||||
theme: theme,
|
theme: theme,
|
||||||
};
|
};
|
||||||
|
|
||||||
const TerminalDiv = styled.div`
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
${tw`bg-neutral-900`};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CommandInput = styled.input`
|
|
||||||
${tw`text-sm transition-colors duration-150 px-2 bg-transparent border-0 border-b-2 border-transparent text-neutral-100 p-2 pl-0 w-full focus:ring-0`}
|
|
||||||
&:focus {
|
|
||||||
${tw`border-cyan-700`};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const TERMINAL_PRELUDE = '\u001b[1m\u001b[33mcontainer@pterodactyl~ \u001b[0m';
|
const TERMINAL_PRELUDE = '\u001b[1m\u001b[33mcontainer@pterodactyl~ \u001b[0m';
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
@ -202,30 +187,25 @@ export default () => {
|
||||||
}, [ connected, instance ]);
|
}, [ connected, instance ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div css={tw`text-xs font-mono relative`}>
|
<div className={styles.terminal}>
|
||||||
<SpinnerOverlay visible={!connected} size={'large'} />
|
<SpinnerOverlay visible={!connected} size={'large'}/>
|
||||||
<div
|
<div className={classNames(styles.container, styles.overflows_container, { 'rounded-b': !canSendCommands })}>
|
||||||
css={[
|
<div id={styles.terminal} ref={ref}/>
|
||||||
tw`rounded-t p-2 bg-black w-full`,
|
|
||||||
!canSendCommands && tw`rounded-b`,
|
|
||||||
]}
|
|
||||||
style={{ minHeight: '16rem' }}
|
|
||||||
>
|
|
||||||
<TerminalDiv id={'terminal'} ref={ref} />
|
|
||||||
</div>
|
</div>
|
||||||
{canSendCommands &&
|
{canSendCommands &&
|
||||||
<div css={tw`rounded-b bg-neutral-900 text-neutral-100 flex items-baseline`}>
|
<div className={classNames('relative', styles.overflows_container)}>
|
||||||
<div css={tw`flex-shrink-0 p-2 font-bold`}>$</div>
|
<input
|
||||||
<div css={tw`w-full`}>
|
className={classNames('peer', styles.command_input)}
|
||||||
<CommandInput
|
type={'text'}
|
||||||
type={'text'}
|
placeholder={'Type a command...'}
|
||||||
placeholder={'Type a command...'}
|
aria-label={'Console command input.'}
|
||||||
aria-label={'Console command input.'}
|
disabled={!instance || !connected}
|
||||||
disabled={!instance || !connected}
|
onKeyDown={handleCommandKeyDown}
|
||||||
onKeyDown={handleCommandKeyDown}
|
autoCorrect={'off'}
|
||||||
autoCorrect={'off'}
|
autoCapitalize={'none'}
|
||||||
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||||
import { Button } from '@/components/elements/button/index';
|
import { Button } from '@/components/elements/button/index';
|
||||||
import Can from '@/components/elements/Can';
|
import Can from '@/components/elements/Can';
|
||||||
import { ServerContext } from '@/state/server';
|
import { ServerContext } from '@/state/server';
|
||||||
import { PowerAction } from '@/components/server/ServerConsole';
|
import { PowerAction } from '@/components/server/console/ServerConsoleContainer';
|
||||||
import { Dialog } from '@/components/elements/dialog';
|
import { Dialog } from '@/components/elements/dialog';
|
||||||
|
|
||||||
interface PowerButtonProps {
|
interface PowerButtonProps {
|
||||||
|
@ -41,7 +41,7 @@ export default ({ className }: PowerButtonProps) => {
|
||||||
</Dialog.Confirm>
|
</Dialog.Confirm>
|
||||||
<Can action={'control.start'}>
|
<Can action={'control.start'}>
|
||||||
<Button
|
<Button
|
||||||
className={'w-24'}
|
className={'w-full sm:w-24'}
|
||||||
disabled={status !== 'offline'}
|
disabled={status !== 'offline'}
|
||||||
onClick={onButtonClick.bind(this, 'start')}
|
onClick={onButtonClick.bind(this, 'start')}
|
||||||
>
|
>
|
||||||
|
@ -50,7 +50,7 @@ export default ({ className }: PowerButtonProps) => {
|
||||||
</Can>
|
</Can>
|
||||||
<Can action={'control.restart'}>
|
<Can action={'control.restart'}>
|
||||||
<Button.Text
|
<Button.Text
|
||||||
className={'w-24'}
|
className={'w-full sm:w-24'}
|
||||||
variant={Button.Variants.Secondary}
|
variant={Button.Variants.Secondary}
|
||||||
disabled={!status}
|
disabled={!status}
|
||||||
onClick={onButtonClick.bind(this, 'restart')}
|
onClick={onButtonClick.bind(this, 'restart')}
|
||||||
|
@ -60,7 +60,7 @@ export default ({ className }: PowerButtonProps) => {
|
||||||
</Can>
|
</Can>
|
||||||
<Can action={'control.stop'}>
|
<Can action={'control.stop'}>
|
||||||
<Button.Danger
|
<Button.Danger
|
||||||
className={'w-24'}
|
className={'w-full sm:w-24'}
|
||||||
variant={killable ? undefined : Button.Variants.Secondary}
|
variant={killable ? undefined : Button.Variants.Secondary}
|
||||||
disabled={status === 'offline'}
|
disabled={status === 'offline'}
|
||||||
onClick={onButtonClick.bind(this, killable ? 'kill' : 'stop')}
|
onClick={onButtonClick.bind(this, killable ? 'kill' : 'stop')}
|
||||||
|
|
|
@ -5,17 +5,16 @@ import ContentContainer from '@/components/elements/ContentContainer';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
import ServerContentBlock from '@/components/elements/ServerContentBlock';
|
import ServerContentBlock from '@/components/elements/ServerContentBlock';
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
import ErrorBoundary from '@/components/elements/ErrorBoundary';
|
|
||||||
import Spinner from '@/components/elements/Spinner';
|
import Spinner from '@/components/elements/Spinner';
|
||||||
import Features from '@feature/Features';
|
import Features from '@feature/Features';
|
||||||
import Console from '@/components/server/Console';
|
import Console from '@/components/server/console/Console';
|
||||||
import StatGraphs from '@/components/server/StatGraphs';
|
import StatGraphs from '@/components/server/console/StatGraphs';
|
||||||
import PowerButtons from '@/components/server/console/PowerButtons';
|
import PowerButtons from '@/components/server/console/PowerButtons';
|
||||||
import ServerDetailsBlock from '@/components/server/ServerDetailsBlock';
|
import ServerDetailsBlock from '@/components/server/console/ServerDetailsBlock';
|
||||||
|
|
||||||
export type PowerAction = 'start' | 'stop' | 'restart' | 'kill';
|
export type PowerAction = 'start' | 'stop' | 'restart' | 'kill';
|
||||||
|
|
||||||
const ServerConsole = () => {
|
const ServerConsoleContainer = () => {
|
||||||
const name = ServerContext.useStoreState(state => state.server.data!.name);
|
const name = ServerContext.useStoreState(state => state.server.data!.name);
|
||||||
const description = ServerContext.useStoreState(state => state.server.data!.description);
|
const description = ServerContext.useStoreState(state => state.server.data!.description);
|
||||||
const isInstalling = ServerContext.useStoreState(state => state.server.data!.isInstalling);
|
const isInstalling = ServerContext.useStoreState(state => state.server.data!.isInstalling);
|
||||||
|
@ -23,20 +22,25 @@ const ServerConsole = () => {
|
||||||
const eggFeatures = ServerContext.useStoreState(state => state.server.data!.eggFeatures, isEqual);
|
const eggFeatures = ServerContext.useStoreState(state => state.server.data!.eggFeatures, isEqual);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ServerContentBlock title={'Console'} className={'grid grid-cols-4 gap-4'}>
|
<ServerContentBlock title={'Console'} className={'flex flex-col gap-2 sm:gap-4'}>
|
||||||
<div className={'flex space-x-4 items-end col-span-4'}>
|
<div className={'flex gap-4 items-end'}>
|
||||||
<div className={'flex-1'}>
|
<div className={'hidden sm:block flex-1'}>
|
||||||
<h1 className={'font-header text-2xl text-gray-50 leading-relaxed line-clamp-1'}>{name}</h1>
|
<h1 className={'font-header text-2xl text-gray-50 leading-relaxed line-clamp-1'}>{name}</h1>
|
||||||
<p className={'text-sm line-clamp-2'}>{description}</p>
|
<p className={'text-sm line-clamp-2'}>{description}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-1'}>
|
<div className={'flex-1'}>
|
||||||
<Can action={[ 'control.start', 'control.stop', 'control.restart' ]} matchAny>
|
<Can action={[ 'control.start', 'control.stop', 'control.restart' ]} matchAny>
|
||||||
<PowerButtons className={'flex justify-end space-x-2'}/>
|
<PowerButtons className={'flex sm:justify-end space-x-2'}/>
|
||||||
</Can>
|
</Can>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'col-span-4 lg:col-span-1'}>
|
<div className={'grid grid-cols-4 gap-2 sm:gap-4'}>
|
||||||
<ServerDetailsBlock className={'flex flex-col space-y-4'}/>
|
<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/>
|
||||||
|
</Spinner.Suspense>
|
||||||
|
</div>
|
||||||
{isInstalling ?
|
{isInstalling ?
|
||||||
<div css={tw`mt-4 rounded bg-yellow-500 p-3`}>
|
<div css={tw`mt-4 rounded bg-yellow-500 p-3`}>
|
||||||
<ContentContainer>
|
<ContentContainer>
|
||||||
|
@ -60,17 +64,14 @@ const ServerConsole = () => {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={'col-span-3'}>
|
<div className={'grid grid-cols-1 md:grid-cols-3 gap-2 sm:gap-4'}>
|
||||||
<Spinner.Suspense>
|
<Spinner.Suspense>
|
||||||
<ErrorBoundary>
|
|
||||||
<Console/>
|
|
||||||
</ErrorBoundary>
|
|
||||||
<StatGraphs/>
|
<StatGraphs/>
|
||||||
</Spinner.Suspense>
|
</Spinner.Suspense>
|
||||||
<Features enabled={eggFeatures}/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Features enabled={eggFeatures}/>
|
||||||
</ServerContentBlock>
|
</ServerContentBlock>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ServerConsole, isEqual);
|
export default memo(ServerConsoleContainer, isEqual);
|
|
@ -14,13 +14,10 @@ import { SocketEvent, SocketRequest } from '@/components/server/events';
|
||||||
import UptimeDuration from '@/components/server/UptimeDuration';
|
import UptimeDuration from '@/components/server/UptimeDuration';
|
||||||
import StatBlock from '@/components/server/console/StatBlock';
|
import StatBlock from '@/components/server/console/StatBlock';
|
||||||
import useWebsocketEvent from '@/plugins/useWebsocketEvent';
|
import useWebsocketEvent from '@/plugins/useWebsocketEvent';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
type Stats = Record<'memory' | 'cpu' | 'disk' | 'uptime' | 'rx' | 'tx', number>;
|
type Stats = Record<'memory' | 'cpu' | 'disk' | 'uptime' | 'rx' | 'tx', number>;
|
||||||
|
|
||||||
interface DetailBlockProps {
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBackgroundColor = (value: number, max: number | null): string | undefined => {
|
const getBackgroundColor = (value: number, max: number | null): string | undefined => {
|
||||||
const delta = !max ? 0 : (value / max);
|
const delta = !max ? 0 : (value / max);
|
||||||
|
|
||||||
|
@ -34,7 +31,7 @@ const getBackgroundColor = (value: number, max: number | null): string | undefin
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ServerDetailsBlock = ({ className }: DetailBlockProps) => {
|
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 status = ServerContext.useStoreState(state => state.status.value);
|
||||||
|
@ -74,7 +71,7 @@ const ServerDetailsBlock = ({ className }: DetailBlockProps) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={classNames('grid grid-cols-6 gap-2 md:gap-4', className)}>
|
||||||
<StatBlock
|
<StatBlock
|
||||||
icon={faClock}
|
icon={faClock}
|
||||||
title={'Uptime'}
|
title={'Uptime'}
|
|
@ -3,6 +3,7 @@ import Icon from '@/components/elements/Icon';
|
||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Tooltip from '@/components/elements/tooltip/Tooltip';
|
import Tooltip from '@/components/elements/tooltip/Tooltip';
|
||||||
|
import styles from './style.module.css';
|
||||||
|
|
||||||
interface StatBlockProps {
|
interface StatBlockProps {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -10,33 +11,26 @@ interface StatBlockProps {
|
||||||
color?: string | undefined;
|
color?: string | undefined;
|
||||||
icon: IconDefinition;
|
icon: IconDefinition;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ title, icon, color, description, children }: StatBlockProps) => {
|
export default ({ title, icon, color, description, className, children }: StatBlockProps) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip arrow placement={'top'} disabled={!description} content={description || ''}>
|
<Tooltip arrow placement={'top'} disabled={!description} content={description || ''}>
|
||||||
<div className={'flex items-center space-x-4 bg-gray-600 rounded p-4 shadow-lg'}>
|
<div className={classNames(styles.stat_block, 'bg-gray-600', className)}>
|
||||||
<div
|
<div className={classNames(styles.status_bar, color || 'bg-gray-700')}/>
|
||||||
className={classNames(
|
<div className={classNames(styles.icon, color || 'bg-gray-700')}>
|
||||||
'transition-colors duration-500',
|
|
||||||
'flex-shrink-0 flex items-center justify-center w-12 h-12 rounded-lg shadow-md',
|
|
||||||
color || 'bg-gray-700',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Icon
|
<Icon
|
||||||
icon={icon}
|
icon={icon}
|
||||||
className={classNames(
|
className={classNames({
|
||||||
'w-6 h-6 m-auto',
|
'text-gray-100': !color || color === 'bg-gray-700',
|
||||||
{
|
'text-gray-50': color && color !== 'bg-gray-700',
|
||||||
'text-gray-100': !color || color === 'bg-gray-700',
|
})}
|
||||||
'text-gray-50': color && color !== 'bg-gray-700',
|
|
||||||
},
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex flex-col justify-center overflow-hidden'}>
|
<div className={'flex flex-col justify-center overflow-hidden'}>
|
||||||
<p className={'font-header leading-tight text-sm text-gray-200'}>{title}</p>
|
<p className={'font-header leading-tight text-xs md:text-sm text-gray-200'}>{title}</p>
|
||||||
<p className={'text-xl font-semibold text-gray-50 truncate'}>
|
<p className={'text-base md:text-xl font-semibold text-gray-50 truncate'}>
|
||||||
{children}
|
{children}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -123,7 +123,7 @@ export default () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div css={tw`mt-4 grid grid-cols-1 sm:grid-cols-2 gap-4`}>
|
<>
|
||||||
<TitledGreyBox title={'Memory usage'} icon={faMemory}>
|
<TitledGreyBox title={'Memory usage'} icon={faMemory}>
|
||||||
{status !== 'offline' ?
|
{status !== 'offline' ?
|
||||||
<canvas
|
<canvas
|
||||||
|
@ -165,6 +165,6 @@ export default () => {
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
</TitledGreyBox>
|
</TitledGreyBox>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -0,0 +1,59 @@
|
||||||
|
.stat_block {
|
||||||
|
@apply flex items-center rounded shadow-lg relative;
|
||||||
|
@apply col-span-3 md:col-span-2 lg:col-span-6;
|
||||||
|
@apply px-3 py-2 md:p-3 lg:p-4;
|
||||||
|
|
||||||
|
& > .status_bar {
|
||||||
|
@apply w-1 h-full absolute left-0 top-0 rounded-l sm:hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .icon {
|
||||||
|
@apply hidden flex-shrink-0 items-center justify-center rounded-lg shadow-md w-12 h-12;
|
||||||
|
@apply transition-colors duration-500;
|
||||||
|
@apply sm:flex sm:mr-4;
|
||||||
|
|
||||||
|
& > svg {
|
||||||
|
@apply w-6 h-6 m-auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal {
|
||||||
|
@apply relative h-full flex flex-col;
|
||||||
|
|
||||||
|
& .overflows_container {
|
||||||
|
@apply -ml-4 sm:ml-0;
|
||||||
|
width: calc(100% + 2rem);
|
||||||
|
|
||||||
|
@screen sm {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .container {
|
||||||
|
@apply rounded-t p-1 sm:p-2 bg-black min-h-[16rem] flex-1 font-mono text-sm;
|
||||||
|
|
||||||
|
& #terminal {
|
||||||
|
@apply h-full;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
@apply w-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-gray-900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .command_icon {
|
||||||
|
@apply flex items-center top-0 left-0 absolute z-10 select-none h-full px-3 transition-colors duration-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .command_input {
|
||||||
|
@apply relative bg-gray-900 px-2 text-gray-100 pl-10 pr-4 py-2 w-full font-mono text-sm sm:rounded-b;
|
||||||
|
@apply focus:ring-0 outline-none focus-visible:outline-none;
|
||||||
|
@apply border-0 border-b-2 border-transparent transition-colors duration-100;
|
||||||
|
@apply active:border-cyan-500 focus:border-cyan-500;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { lazy } from 'react';
|
import React, { lazy } from 'react';
|
||||||
import ServerConsole from '@/components/server/ServerConsole';
|
import ServerConsole from '@/components/server/console/ServerConsoleContainer';
|
||||||
import DatabasesContainer from '@/components/server/databases/DatabasesContainer';
|
import DatabasesContainer from '@/components/server/databases/DatabasesContainer';
|
||||||
import ScheduleContainer from '@/components/server/schedules/ScheduleContainer';
|
import ScheduleContainer from '@/components/server/schedules/ScheduleContainer';
|
||||||
import UsersContainer from '@/components/server/users/UsersContainer';
|
import UsersContainer from '@/components/server/users/UsersContainer';
|
||||||
|
|
Loading…
Reference in New Issue