Get server listing design working mostly correct
This commit is contained in:
parent
94e3acb9c4
commit
d93e804a31
9 changed files with 170 additions and 188 deletions
20
resources/scripts/assets/css/GlobalStylesheet.ts
Normal file
20
resources/scripts/assets/css/GlobalStylesheet.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import { createGlobalStyle } from 'styled-components/macro';
|
||||||
|
|
||||||
|
export default createGlobalStyle`
|
||||||
|
@import url('//fonts.googleapis.com/css?family=Rubik:300,400,500&display=swap');
|
||||||
|
@import url('//fonts.googleapis.com/css?family=IBM+Plex+Mono|IBM+Plex+Sans:500&display=swap');
|
||||||
|
|
||||||
|
body {
|
||||||
|
${tw`font-sans bg-neutral-800 text-neutral-200`};
|
||||||
|
letter-spacing: 0.015em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
${tw`font-medium tracking-normal font-header`};
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
${tw`text-neutral-200 leading-snug font-sans`};
|
||||||
|
}
|
||||||
|
`;
|
|
@ -11,6 +11,7 @@ import { SiteSettings } from '@/state/settings';
|
||||||
import ProgressBar from '@/components/elements/ProgressBar';
|
import ProgressBar from '@/components/elements/ProgressBar';
|
||||||
import NotFound from '@/components/screens/NotFound';
|
import NotFound from '@/components/screens/NotFound';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
import GlobalStylesheet from '@/assets/css/GlobalStylesheet';
|
||||||
|
|
||||||
interface ExtendedWindow extends Window {
|
interface ExtendedWindow extends Window {
|
||||||
SiteConfiguration?: SiteSettings;
|
SiteConfiguration?: SiteSettings;
|
||||||
|
@ -48,21 +49,24 @@ const App = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StoreProvider store={store}>
|
<>
|
||||||
<Provider store={store}>
|
<GlobalStylesheet/>
|
||||||
<ProgressBar/>
|
<StoreProvider store={store}>
|
||||||
<div css={tw`mx-auto w-auto`}>
|
<Provider store={store}>
|
||||||
<BrowserRouter basename={'/'} key={'root-router'}>
|
<ProgressBar/>
|
||||||
<Switch>
|
<div css={tw`mx-auto w-auto`}>
|
||||||
<Route path="/server/:id" component={ServerRouter}/>
|
<BrowserRouter basename={'/'} key={'root-router'}>
|
||||||
<Route path="/auth" component={AuthenticationRouter}/>
|
<Switch>
|
||||||
<Route path="/" component={DashboardRouter}/>
|
<Route path="/server/:id" component={ServerRouter}/>
|
||||||
<Route path={'*'} component={NotFound}/>
|
<Route path="/auth" component={AuthenticationRouter}/>
|
||||||
</Switch>
|
<Route path="/" component={DashboardRouter}/>
|
||||||
</BrowserRouter>
|
<Route path={'*'} component={NotFound}/>
|
||||||
</div>
|
</Switch>
|
||||||
</Provider>
|
</BrowserRouter>
|
||||||
</StoreProvider>
|
</div>
|
||||||
|
</Provider>
|
||||||
|
</StoreProvider>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,51 +1,76 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Link, NavLink } from 'react-router-dom';
|
import { Link, NavLink } from 'react-router-dom';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faLayerGroup } from '@fortawesome/free-solid-svg-icons/faLayerGroup';
|
import { faCogs, faLayerGroup, faSignOutAlt, faUserCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faUserCircle } from '@fortawesome/free-solid-svg-icons/faUserCircle';
|
|
||||||
import { faSignOutAlt } from '@fortawesome/free-solid-svg-icons/faSignOutAlt';
|
|
||||||
import { faSwatchbook } from '@fortawesome/free-solid-svg-icons/faSwatchbook';
|
|
||||||
import { faCogs } from '@fortawesome/free-solid-svg-icons/faCogs';
|
|
||||||
import { useStoreState } from 'easy-peasy';
|
import { useStoreState } from 'easy-peasy';
|
||||||
import { ApplicationStore } from '@/state';
|
import { ApplicationStore } from '@/state';
|
||||||
import { faSearch } from '@fortawesome/free-solid-svg-icons/faSearch';
|
|
||||||
import SearchContainer from '@/components/dashboard/search/SearchContainer';
|
import SearchContainer from '@/components/dashboard/search/SearchContainer';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import styled from 'styled-components/macro';
|
||||||
|
import * as config from '@/../../tailwind.config.js';
|
||||||
|
|
||||||
|
const Navigation = styled.div`
|
||||||
|
${tw`w-full bg-neutral-900 shadow-md`};
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
${tw`mx-auto w-full flex items-center`};
|
||||||
|
}
|
||||||
|
|
||||||
|
& #logo {
|
||||||
|
${tw`flex-1`};
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
${tw`text-2xl font-header px-4 no-underline text-neutral-200 hover:text-neutral-100 transition-colors duration-150`};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const RightNavigation = styled.div`
|
||||||
|
${tw`flex h-full items-center justify-center`};
|
||||||
|
|
||||||
|
& > a, & > .navigation-link {
|
||||||
|
${tw`flex items-center h-full no-underline text-neutral-300 px-6 cursor-pointer transition-all duration-150`};
|
||||||
|
|
||||||
|
&:active, &:hover {
|
||||||
|
${tw`text-neutral-100 bg-black`};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active, &:hover, &.active {
|
||||||
|
box-shadow: inset 0 -2px ${config.theme.colors.cyan['700']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const user = useStoreState((state: ApplicationStore) => state.user.data!);
|
const user = useStoreState((state: ApplicationStore) => state.user.data!);
|
||||||
const name = useStoreState((state: ApplicationStore) => state.settings.data!.name);
|
const name = useStoreState((state: ApplicationStore) => state.settings.data!.name);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={'navigation'}>
|
<Navigation>
|
||||||
<div className={'mx-auto w-full flex items-center'} style={{ maxWidth: '1200px', height: '3.5rem' }}>
|
<div css={tw`mx-auto w-full flex items-center`} style={{ maxWidth: '1200px', height: '3.5rem' }}>
|
||||||
<div id={'logo'}>
|
<div id={'logo'}>
|
||||||
<Link to={'/'}>
|
<Link to={'/'}>
|
||||||
{name}
|
{name}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className={'right-navigation'}>
|
<RightNavigation>
|
||||||
<SearchContainer/>
|
<SearchContainer/>
|
||||||
<NavLink to={'/'} exact={true}>
|
<NavLink to={'/'} exact>
|
||||||
<FontAwesomeIcon icon={faLayerGroup}/>
|
<FontAwesomeIcon icon={faLayerGroup}/>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<NavLink to={'/account'}>
|
<NavLink to={'/account'}>
|
||||||
<FontAwesomeIcon icon={faUserCircle}/>
|
<FontAwesomeIcon icon={faUserCircle}/>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{user.rootAdmin &&
|
{user.rootAdmin &&
|
||||||
<a href={'/admin'} target={'_blank'}>
|
<a href={'/admin'} target={'_blank'} rel={'noreferrer'}>
|
||||||
<FontAwesomeIcon icon={faCogs}/>
|
<FontAwesomeIcon icon={faCogs}/>
|
||||||
</a>
|
</a>
|
||||||
}
|
}
|
||||||
{process.env.NODE_ENV !== 'production' &&
|
|
||||||
<NavLink to={'/design'}>
|
|
||||||
<FontAwesomeIcon icon={faSwatchbook}/>
|
|
||||||
</NavLink>
|
|
||||||
}
|
|
||||||
<a href={'/auth/logout'}>
|
<a href={'/auth/logout'}>
|
||||||
<FontAwesomeIcon icon={faSignOutAlt}/>
|
<FontAwesomeIcon icon={faSignOutAlt}/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</RightNavigation>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Navigation>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ import FlashMessageRender from '@/components/FlashMessageRender';
|
||||||
import { useStoreState } from 'easy-peasy';
|
import { useStoreState } from 'easy-peasy';
|
||||||
import { usePersistedState } from '@/plugins/usePersistedState';
|
import { usePersistedState } from '@/plugins/usePersistedState';
|
||||||
import Switch from '@/components/elements/Switch';
|
import Switch from '@/components/elements/Switch';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const { addError, clearFlashes } = useFlash();
|
const { addError, clearFlashes } = useFlash();
|
||||||
|
@ -37,10 +38,10 @@ export default () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContentBlock>
|
<PageContentBlock>
|
||||||
<FlashMessageRender className={'mb-4'}/>
|
<FlashMessageRender css={tw`mb-4`}/>
|
||||||
{rootAdmin &&
|
{rootAdmin &&
|
||||||
<div className={'mb-2 flex justify-end items-center'}>
|
<div css={tw`mb-2 flex justify-end items-center`}>
|
||||||
<p className={'uppercase text-xs text-neutral-400 mr-2'}>
|
<p css={tw`uppercase text-xs text-neutral-400 mr-2`}>
|
||||||
{showAdmin ? 'Showing all servers' : 'Showing your servers'}
|
{showAdmin ? 'Showing all servers' : 'Showing your servers'}
|
||||||
</p>
|
</p>
|
||||||
<Switch
|
<Switch
|
||||||
|
@ -51,14 +52,16 @@ export default () => {
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{loading ?
|
{loading ?
|
||||||
<Spinner centered={true} size={'large'}/>
|
<Spinner centered size={'large'}/>
|
||||||
:
|
:
|
||||||
servers.length > 0 ?
|
servers.length > 0 ?
|
||||||
servers.map(server => (
|
servers.map((server, index) => (
|
||||||
<ServerRow key={server.uuid} server={server} className={'mt-2'}/>
|
<div key={server.uuid} css={index > 0 ? tw`mt-2` : undefined}>
|
||||||
|
<ServerRow server={server}/>
|
||||||
|
</div>
|
||||||
))
|
))
|
||||||
:
|
:
|
||||||
<p className={'text-center text-sm text-neutral-400'}>
|
<p css={tw`text-center text-sm text-neutral-400`}>
|
||||||
There are no servers associated with your account.
|
There are no servers associated with your account.
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
import * as React from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import ContentBox from '@/components/elements/ContentBox';
|
|
||||||
|
|
||||||
export default class DesignElementsContainer extends React.PureComponent {
|
|
||||||
render () {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<div className={'my-10'}>
|
|
||||||
<div className={'flex'}>
|
|
||||||
<ContentBox
|
|
||||||
className={'flex-1 mr-4'}
|
|
||||||
title={'A Special Announcement'}
|
|
||||||
borderColor={'border-primary-400'}
|
|
||||||
>
|
|
||||||
<p className={'text-neutral-200 text-sm'}>
|
|
||||||
Your demands have been received: Dark Mode will be default in Pterodactyl 0.8!
|
|
||||||
</p>
|
|
||||||
<p><Link to={'/'}>Back</Link></p>
|
|
||||||
</ContentBox>
|
|
||||||
<div className={'ml-4 flex-1'}>
|
|
||||||
<h2 className={'text-neutral-300 mb-2 px-4'}>Form Elements</h2>
|
|
||||||
<div className={'bg-neutral-700 p-4 rounded shadow-lg border-t-4 border-primary-400'}>
|
|
||||||
<label className={'uppercase text-neutral-200'}>Email</label>
|
|
||||||
<input type={'text'} className={'input-dark'}/>
|
|
||||||
<p className={'input-help'}>
|
|
||||||
This is some descriptive helper text to explain how things work.
|
|
||||||
</p>
|
|
||||||
<div className={'mt-6'}/>
|
|
||||||
<label className={'uppercase text-neutral-200'}>Username</label>
|
|
||||||
<input type={'text'} className={'input-dark error'}/>
|
|
||||||
<p className={'input-help'}>
|
|
||||||
This field has an error.
|
|
||||||
</p>
|
|
||||||
<div className={'mt-6'}/>
|
|
||||||
<label className={'uppercase text-neutral-200'}>Disabled Field</label>
|
|
||||||
<input type={'text'} className={'input-dark'} disabled={true}/>
|
|
||||||
<div className={'mt-6'}/>
|
|
||||||
<label className={'uppercase text-neutral-200'}>Select</label>
|
|
||||||
<select className={'input-dark'}>
|
|
||||||
<option>Option 1</option>
|
|
||||||
<option>Option 2</option>
|
|
||||||
<option>Option 3</option>
|
|
||||||
</select>
|
|
||||||
<div className={'mt-6'}/>
|
|
||||||
<label className={'uppercase text-neutral-200'}>Textarea</label>
|
|
||||||
<textarea className={'input-dark h-32'}></textarea>
|
|
||||||
<div className={'mt-6'}/>
|
|
||||||
<button className={'btn btn-primary btn-sm'}>
|
|
||||||
Blue
|
|
||||||
</button>
|
|
||||||
<button className={'btn btn-grey btn-sm ml-2'}>
|
|
||||||
Grey
|
|
||||||
</button>
|
|
||||||
<button className={'btn btn-green btn-sm ml-2'}>
|
|
||||||
Green
|
|
||||||
</button>
|
|
||||||
<button className={'btn btn-red btn-sm ml-2'}>
|
|
||||||
Red
|
|
||||||
</button>
|
|
||||||
<div className={'mt-6'}/>
|
|
||||||
<button className={'btn btn-secondary btn-sm'}>
|
|
||||||
Secondary
|
|
||||||
</button>
|
|
||||||
<button className={'btn btn-secondary btn-red btn-sm ml-2'}>
|
|
||||||
Secondary Danger
|
|
||||||
</button>
|
|
||||||
<div className={'mt-6'}/>
|
|
||||||
<button className={'btn btn-primary btn-lg'}>
|
|
||||||
Large
|
|
||||||
</button>
|
|
||||||
<button className={'btn btn-primary btn-xs ml-2'}>
|
|
||||||
Tiny
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,6 +11,8 @@ import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||||
import getServerResourceUsage, { ServerStats } from '@/api/server/getServerResourceUsage';
|
import getServerResourceUsage, { ServerStats } from '@/api/server/getServerResourceUsage';
|
||||||
import { bytesToHuman } from '@/helpers';
|
import { bytesToHuman } from '@/helpers';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
import GreyRowBox from '@/components/elements/GreyRowBox';
|
||||||
|
|
||||||
// Determines if the current value is in an alarm threshold so we can show it in red rather
|
// Determines if the current value is in an alarm threshold so we can show it in red rather
|
||||||
// than the more faded default style.
|
// than the more faded default style.
|
||||||
|
@ -20,7 +22,7 @@ const isAlarmState = (current: number, limit: number): boolean => {
|
||||||
return current / limitInBytes >= 0.90;
|
return current / limitInBytes >= 0.90;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ({ server, className }: { server: Server; className: string | undefined }) => {
|
export default ({ server }: { server: Server }) => {
|
||||||
const interval = useRef<number>(null);
|
const interval = useRef<number>(null);
|
||||||
const [ stats, setStats ] = useState<ServerStats | null>(null);
|
const [ stats, setStats ] = useState<ServerStats | null>(null);
|
||||||
const [ statsError, setStatsError ] = useState(false);
|
const [ statsError, setStatsError ] = useState(false);
|
||||||
|
@ -37,7 +39,6 @@ export default ({ server, className }: { server: Server; className: string | und
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getStats().then(() => {
|
getStats().then(() => {
|
||||||
// @ts-ignore
|
|
||||||
interval.current = setInterval(() => getStats(), 20000);
|
interval.current = setInterval(() => getStats(), 20000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -52,21 +53,21 @@ export default ({ server, className }: { server: Server; className: string | und
|
||||||
alarms.memory = isAlarmState(stats.memoryUsageInBytes, server.limits.memory);
|
alarms.memory = isAlarmState(stats.memoryUsageInBytes, server.limits.memory);
|
||||||
alarms.disk = server.limits.disk === 0 ? false : isAlarmState(stats.diskUsageInBytes, server.limits.disk);
|
alarms.disk = server.limits.disk === 0 ? false : isAlarmState(stats.diskUsageInBytes, server.limits.disk);
|
||||||
}
|
}
|
||||||
const disklimit = server.limits.disk != 0 ? bytesToHuman(server.limits.disk * 1000 * 1000) : "Unlimited";
|
const disklimit = server.limits.disk !== 0 ? bytesToHuman(server.limits.disk * 1000 * 1000) : 'Unlimited';
|
||||||
const memorylimit = server.limits.memory != 0 ? bytesToHuman(server.limits.memory * 1000 * 1000) : "Unlimited";
|
const memorylimit = server.limits.memory !== 0 ? bytesToHuman(server.limits.memory * 1000 * 1000) : 'Unlimited';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={`/server/${server.id}`} className={`grey-row-box cursor-pointer ${className}`}>
|
<GreyRowBox as={Link} to={`/server/${server.id}`}>
|
||||||
<div className={'icon'}>
|
<div className={'icon'}>
|
||||||
<FontAwesomeIcon icon={faServer}/>
|
<FontAwesomeIcon icon={faServer}/>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-1 ml-4'}>
|
<div css={tw`flex-1 ml-4`}>
|
||||||
<p className={'text-lg'}>{server.name}</p>
|
<p css={tw`text-lg`}>{server.name}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={'w-1/4 overflow-hidden'}>
|
<div css={tw`w-1/4 overflow-hidden`}>
|
||||||
<div className={'flex ml-4'}>
|
<div css={tw`flex ml-4`}>
|
||||||
<FontAwesomeIcon icon={faEthernet} className={'text-neutral-500'}/>
|
<FontAwesomeIcon icon={faEthernet} css={tw`text-neutral-500`}/>
|
||||||
<p className={'text-sm text-neutral-400 ml-2'}>
|
<p css={tw`text-sm text-neutral-400 ml-2`}>
|
||||||
{
|
{
|
||||||
server.allocations.filter(alloc => alloc.default).map(allocation => (
|
server.allocations.filter(alloc => alloc.default).map(allocation => (
|
||||||
<span key={allocation.ip + allocation.port.toString()}>{allocation.alias || allocation.ip}:{allocation.port}</span>
|
<span key={allocation.ip + allocation.port.toString()}>{allocation.alias || allocation.ip}:{allocation.port}</span>
|
||||||
|
@ -75,85 +76,88 @@ export default ({ server, className }: { server: Server; className: string | und
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={'w-1/3 flex items-baseline relative'}>
|
<div css={tw`w-1/3 flex items-baseline relative`}>
|
||||||
{!stats ?
|
{!stats ?
|
||||||
!statsError ?
|
!statsError ?
|
||||||
<SpinnerOverlay size={'tiny'} visible={true} backgroundOpacity={0.25}/>
|
<SpinnerOverlay size={'tiny'} visible={true} backgroundOpacity={0.25}/>
|
||||||
:
|
:
|
||||||
server.isInstalling ?
|
server.isInstalling ?
|
||||||
<div className={'flex-1 text-center'}>
|
<div css={tw`flex-1 text-center`}>
|
||||||
<span className={'bg-neutral-500 rounded px-2 py-1 text-neutral-100 text-xs'}>
|
<span css={tw`bg-neutral-500 rounded px-2 py-1 text-neutral-100 text-xs`}>
|
||||||
Installing
|
Installing
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<div className={'flex-1 text-center'}>
|
<div css={tw`flex-1 text-center`}>
|
||||||
<span className={'bg-red-500 rounded px-2 py-1 text-red-100 text-xs'}>
|
<span css={tw`bg-red-500 rounded px-2 py-1 text-red-100 text-xs`}>
|
||||||
{server.isSuspended ? 'Suspended' : 'Connection Error'}
|
{server.isSuspended ? 'Suspended' : 'Connection Error'}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className={'flex-1 flex ml-4 justify-center'}>
|
<div css={tw`flex-1 flex ml-4 justify-center`}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faMicrochip}
|
icon={faMicrochip}
|
||||||
className={classNames({
|
css={[
|
||||||
'text-neutral-500': !alarms.cpu,
|
!alarms.cpu && tw`text-neutral-500`,
|
||||||
'text-red-400': alarms.cpu,
|
alarms.cpu && tw`text-red-400`,
|
||||||
})}
|
]}
|
||||||
/>
|
/>
|
||||||
<p
|
<p
|
||||||
className={classNames('text-sm ml-2', {
|
css={[
|
||||||
'text-neutral-400': !alarms.cpu,
|
tw`text-sm ml-2`,
|
||||||
'text-white': alarms.cpu,
|
!alarms.cpu && tw`text-neutral-400`,
|
||||||
})}
|
alarms.cpu && tw`text-white`,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
{stats.cpuUsagePercent} %
|
{stats.cpuUsagePercent} %
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-1 ml-4'}>
|
<div css={tw`flex-1 ml-4`}>
|
||||||
<div className={'flex justify-center'}>
|
<div css={tw`flex justify-center`}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faMemory}
|
icon={faMemory}
|
||||||
className={classNames({
|
css={[
|
||||||
'text-neutral-500': !alarms.memory,
|
!alarms.memory && tw`text-neutral-500`,
|
||||||
'text-red-400': alarms.memory,
|
alarms.memory && tw`text-red-400`,
|
||||||
})}
|
]}
|
||||||
/>
|
/>
|
||||||
<p
|
<p
|
||||||
className={classNames('text-sm ml-2', {
|
css={[
|
||||||
'text-neutral-400': !alarms.memory,
|
tw`text-sm ml-2`,
|
||||||
'text-white': alarms.memory,
|
!alarms.memory && tw`text-neutral-400`,
|
||||||
})}
|
alarms.memory && tw`text-white`,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
{bytesToHuman(stats.memoryUsageInBytes)}
|
{bytesToHuman(stats.memoryUsageInBytes)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className={'text-xs text-neutral-600 text-center mt-1'}>of {memorylimit}</p>
|
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {memorylimit}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex-1 ml-4'}>
|
<div css={tw`flex-1 ml-4`}>
|
||||||
<div className={'flex justify-center'}>
|
<div css={tw`flex justify-center`}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faHdd}
|
icon={faHdd}
|
||||||
className={classNames({
|
css={[
|
||||||
'text-neutral-500': !alarms.disk,
|
!alarms.disk && tw`text-neutral-500`,
|
||||||
'text-red-400': alarms.disk,
|
alarms.disk && tw`text-red-400`,
|
||||||
})}
|
]}
|
||||||
/>
|
/>
|
||||||
<p
|
<p
|
||||||
className={classNames('text-sm ml-2', {
|
css={[
|
||||||
'text-neutral-400': !alarms.disk,
|
tw`text-sm ml-2`,
|
||||||
'text-white': alarms.disk,
|
!alarms.disk && tw`text-neutral-400`,
|
||||||
})}
|
alarms.disk && tw`text-white`,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
{bytesToHuman(stats.diskUsageInBytes)}
|
{bytesToHuman(stats.diskUsageInBytes)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p className={'text-xs text-neutral-600 text-center mt-1'}>of {disklimit}</p>
|
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {disklimit}</p>
|
||||||
</div>
|
</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</GreyRowBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
14
resources/scripts/components/elements/GreyRowBox.tsx
Normal file
14
resources/scripts/components/elements/GreyRowBox.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import styled from 'styled-components/macro';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
|
export default styled.div`
|
||||||
|
${tw`flex rounded no-underline text-neutral-200 items-center bg-neutral-700 p-4 border border-transparent transition-colors duration-150`};
|
||||||
|
|
||||||
|
&:not(.no-hover):hover {
|
||||||
|
${tw`border-neutral-500`};
|
||||||
|
}
|
||||||
|
|
||||||
|
& > div.icon {
|
||||||
|
${tw`rounded-full bg-neutral-500 p-3`};
|
||||||
|
}
|
||||||
|
`;
|
|
@ -1,26 +1,22 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ContentContainer from '@/components/elements/ContentContainer';
|
import ContentContainer from '@/components/elements/ContentContainer';
|
||||||
import { CSSTransition } from 'react-transition-group';
|
import { CSSTransition } from 'react-transition-group';
|
||||||
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
interface Props {
|
export default ({ children }): React.FC => (
|
||||||
children: React.ReactNode;
|
<CSSTransition timeout={250} classNames={'fade'} appear in>
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ({ className, children }: Props) => (
|
|
||||||
<CSSTransition timeout={250} classNames={'fade'} appear={true} in={true}>
|
|
||||||
<>
|
<>
|
||||||
<ContentContainer className={`my-10 ${className}`}>
|
<ContentContainer css={tw`my-10`}>
|
||||||
{children}
|
{children}
|
||||||
</ContentContainer>
|
</ContentContainer>
|
||||||
<ContentContainer className={'mb-4'}>
|
<ContentContainer css={tw`mb-4`}>
|
||||||
<p className={'text-center text-neutral-500 text-xs'}>
|
<p css={tw`text-center text-neutral-500 text-xs`}>
|
||||||
© 2015 - 2020
|
© 2015 - 2020
|
||||||
<a
|
<a
|
||||||
rel={'noopener nofollow'}
|
rel={'noopener nofollow noreferrer'}
|
||||||
href={'https://pterodactyl.io'}
|
href={'https://pterodactyl.io'}
|
||||||
target={'_blank'}
|
target={'_blank'}
|
||||||
className={'no-underline text-neutral-500 hover:text-neutral-300'}
|
css={tw`no-underline text-neutral-500 hover:text-neutral-300`}
|
||||||
>
|
>
|
||||||
Pterodactyl Software
|
Pterodactyl Software
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
import { NavLink, Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||||
import DesignElementsContainer from '@/components/dashboard/DesignElementsContainer';
|
|
||||||
import AccountOverviewContainer from '@/components/dashboard/AccountOverviewContainer';
|
import AccountOverviewContainer from '@/components/dashboard/AccountOverviewContainer';
|
||||||
import NavigationBar from '@/components/NavigationBar';
|
import NavigationBar from '@/components/NavigationBar';
|
||||||
import DashboardContainer from '@/components/dashboard/DashboardContainer';
|
import DashboardContainer from '@/components/dashboard/DashboardContainer';
|
||||||
|
@ -9,13 +8,13 @@ import AccountApiContainer from '@/components/dashboard/AccountApiContainer';
|
||||||
import NotFound from '@/components/screens/NotFound';
|
import NotFound from '@/components/screens/NotFound';
|
||||||
|
|
||||||
export default ({ location }: RouteComponentProps) => (
|
export default ({ location }: RouteComponentProps) => (
|
||||||
<React.Fragment>
|
<>
|
||||||
<NavigationBar/>
|
<NavigationBar/>
|
||||||
{location.pathname.startsWith('/account') &&
|
{location.pathname.startsWith('/account') &&
|
||||||
<div id={'sub-navigation'}>
|
<div id={'sub-navigation'}>
|
||||||
<div className={'items'}>
|
<div className={'items'}>
|
||||||
<NavLink to={`/account`} exact>Settings</NavLink>
|
<NavLink to={'/account'} exact>Settings</NavLink>
|
||||||
<NavLink to={`/account/api`}>API Credentials</NavLink>
|
<NavLink to={'/account/api'}>API Credentials</NavLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -24,9 +23,8 @@ export default ({ location }: RouteComponentProps) => (
|
||||||
<Route path={'/'} component={DashboardContainer} exact/>
|
<Route path={'/'} component={DashboardContainer} exact/>
|
||||||
<Route path={'/account'} component={AccountOverviewContainer} exact/>
|
<Route path={'/account'} component={AccountOverviewContainer} exact/>
|
||||||
<Route path={'/account/api'} component={AccountApiContainer} exact/>
|
<Route path={'/account/api'} component={AccountApiContainer} exact/>
|
||||||
<Route path={'/design'} component={DesignElementsContainer}/>
|
|
||||||
<Route path={'*'} component={NotFound}/>
|
<Route path={'*'} component={NotFound}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
</TransitionRouter>
|
</TransitionRouter>
|
||||||
</React.Fragment>
|
</>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue