Mobile responsiveness fixes
This commit is contained in:
parent
47d59ca20f
commit
2a42ef5c62
5 changed files with 66 additions and 72 deletions
|
@ -9,20 +9,12 @@ import tw from 'twin.macro';
|
||||||
import GreyRowBox from '@/components/elements/GreyRowBox';
|
import GreyRowBox from '@/components/elements/GreyRowBox';
|
||||||
import Spinner from '@/components/elements/Spinner';
|
import Spinner from '@/components/elements/Spinner';
|
||||||
import styled from 'styled-components/macro';
|
import styled from 'styled-components/macro';
|
||||||
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
|
||||||
import isEqual from 'react-fast-compare';
|
import isEqual from 'react-fast-compare';
|
||||||
|
|
||||||
// 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.
|
||||||
const isAlarmState = (current: number, limit: number): boolean => limit > 0 && (current / (limit * 1024 * 1024) >= 0.90);
|
const isAlarmState = (current: number, limit: number): boolean => limit > 0 && (current / (limit * 1024 * 1024) >= 0.90);
|
||||||
|
|
||||||
interface IconProps {
|
|
||||||
icon: IconProp;
|
|
||||||
usage: number;
|
|
||||||
limit: string;
|
|
||||||
isAlarm: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Icon = memo(styled(FontAwesomeIcon)<{ $alarm: boolean }>`
|
const Icon = memo(styled(FontAwesomeIcon)<{ $alarm: boolean }>`
|
||||||
${props => props.$alarm ? tw`text-red-400` : tw`text-neutral-500`};
|
${props => props.$alarm ? tw`text-red-400` : tw`text-neutral-500`};
|
||||||
`, isEqual);
|
`, isEqual);
|
||||||
|
@ -69,29 +61,31 @@ export default ({ server, className }: { server: Server; className?: string }) =
|
||||||
const memorylimit = server.limits.memory !== 0 ? megabytesToHuman(server.limits.memory) : 'Unlimited';
|
const memorylimit = server.limits.memory !== 0 ? megabytesToHuman(server.limits.memory) : 'Unlimited';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GreyRowBox as={Link} to={`/server/${server.id}`} className={className}>
|
<GreyRowBox as={Link} to={`/server/${server.id}`} className={className} css={tw`grid grid-cols-12 gap-4`}>
|
||||||
<div className={'icon'} css={tw`hidden md:block`}>
|
<div css={tw`flex items-center col-span-11 sm:col-span-5 lg:col-span-6`}>
|
||||||
<FontAwesomeIcon icon={faServer}/>
|
<div className={'icon'} css={tw`mr-4`}>
|
||||||
</div>
|
<FontAwesomeIcon icon={faServer}/>
|
||||||
<div css={tw`flex-1 md:ml-4`}>
|
</div>
|
||||||
<p css={tw`text-lg break-all`}>{server.name}</p>
|
<div>
|
||||||
{!!server.description &&
|
<p css={tw`text-lg break-words`}>{server.name}</p>
|
||||||
<p css={tw`text-sm text-neutral-300 break-all`}>{server.description}</p>
|
{!!server.description &&
|
||||||
}
|
<p css={tw`text-sm text-neutral-300 break-all`}>{server.description}</p>
|
||||||
</div>
|
}
|
||||||
<div css={tw`w-48 overflow-hidden self-start hidden lg:block`}>
|
|
||||||
<div css={tw`flex ml-4 justify-end`}>
|
|
||||||
<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 => (
|
|
||||||
<span key={allocation.ip + allocation.port.toString()}>{allocation.alias || allocation.ip}:{allocation.port}</span>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div css={tw`w-1/3 sm:w-1/2 lg:w-1/3 flex items-baseline justify-center relative`}>
|
<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()}>
|
||||||
|
{allocation.alias || allocation.ip}:{allocation.port}
|
||||||
|
</React.Fragment>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div css={tw`hidden col-span-7 lg:col-span-4 sm:flex items-baseline justify-center`}>
|
||||||
{!stats ?
|
{!stats ?
|
||||||
!statsError ?
|
!statsError ?
|
||||||
<Spinner size={'small'}/>
|
<Spinner size={'small'}/>
|
||||||
|
@ -134,21 +128,19 @@ export default ({ server, className }: { server: Server; className?: string }) =
|
||||||
</div>
|
</div>
|
||||||
<p css={tw`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>
|
||||||
<div css={tw`flex-1 flex justify-end sm:hidden`}>
|
|
||||||
<div css={tw`flex items-end text-right`}>
|
|
||||||
<div
|
|
||||||
css={[
|
|
||||||
tw`w-3 h-3 rounded-full`,
|
|
||||||
(!stats?.status || stats?.status === 'offline')
|
|
||||||
? tw`bg-red-500`
|
|
||||||
: (stats?.status === 'running' ? tw`bg-green-500` : tw`bg-yellow-500`),
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div css={tw`col-span-1 sm:hidden flex justify-end`}>
|
||||||
|
<div
|
||||||
|
css={[
|
||||||
|
tw`w-3 h-3 rounded-full`,
|
||||||
|
(!stats?.status || stats?.status === 'offline')
|
||||||
|
? tw`bg-red-500`
|
||||||
|
: (stats?.status === 'running' ? tw`bg-green-500` : tw`bg-yellow-500`),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</GreyRowBox>
|
</GreyRowBox>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,11 +2,11 @@ import styled from 'styled-components/macro';
|
||||||
import tw from 'twin.macro';
|
import tw from 'twin.macro';
|
||||||
|
|
||||||
export default styled.div<{ $hoverable?: boolean }>`
|
export default styled.div<{ $hoverable?: boolean }>`
|
||||||
${tw`flex rounded no-underline text-neutral-200 items-center bg-neutral-700 p-4 border border-transparent transition-colors duration-150`};
|
${tw`flex rounded no-underline text-neutral-200 items-center bg-neutral-700 p-4 border border-transparent transition-colors duration-150 overflow-hidden`};
|
||||||
|
|
||||||
${props => props.$hoverable !== false && tw`hover:border-neutral-500`};
|
${props => props.$hoverable !== false && tw`hover:border-neutral-500`};
|
||||||
|
|
||||||
& > div.icon {
|
& .icon {
|
||||||
${tw`rounded-full bg-neutral-500 p-3`};
|
${tw`rounded-full bg-neutral-500 p-3`};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -40,33 +40,35 @@ export default ({ backup, className }: Props) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GreyRowBox css={tw`flex items-center`} className={className}>
|
<GreyRowBox css={tw`flex-wrap md:flex-no-wrap items-center`} className={className}>
|
||||||
<div css={tw`mr-4`}>
|
<div css={tw`flex items-center truncate w-full md:flex-1`}>
|
||||||
{backup.completedAt ?
|
<div css={tw`mr-4`}>
|
||||||
<FontAwesomeIcon icon={faArchive} css={tw`text-neutral-300 hidden md:block`}/>
|
{backup.completedAt ?
|
||||||
:
|
<FontAwesomeIcon icon={faArchive} css={tw`text-neutral-300`}/>
|
||||||
<Spinner size={'small'}/>
|
:
|
||||||
}
|
<Spinner size={'small'}/>
|
||||||
</div>
|
|
||||||
<div css={tw`flex-1`}>
|
|
||||||
<p css={tw`flex items-center text-sm mb-1`}>
|
|
||||||
{!backup.isSuccessful &&
|
|
||||||
<span css={tw`bg-red-500 py-px px-2 rounded-full text-white text-xs uppercase border border-red-600 mr-2`}>
|
|
||||||
Failed
|
|
||||||
</span>
|
|
||||||
}
|
}
|
||||||
<div css={tw`break-all`}>
|
</div>
|
||||||
{backup.name}
|
<div css={tw`flex flex-col truncate`}>
|
||||||
|
<div css={tw`flex items-center text-sm mb-1`}>
|
||||||
|
{!backup.isSuccessful &&
|
||||||
|
<span css={tw`bg-red-500 py-px px-2 rounded-full text-white text-xs uppercase border border-red-600 mr-2`}>
|
||||||
|
Failed
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
<p css={tw`break-all truncate`}>
|
||||||
|
{backup.name}
|
||||||
|
</p>
|
||||||
|
{(backup.completedAt && backup.isSuccessful) &&
|
||||||
|
<span css={tw`ml-3 text-neutral-300 text-xs font-thin hidden sm:inline`}>{bytesToHuman(backup.bytes)}</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
{(backup.completedAt && backup.isSuccessful) &&
|
<p css={tw`mt-1 md:mt-0 text-xs text-neutral-400 font-mono truncate`}>
|
||||||
<span css={tw`ml-3 text-neutral-300 text-xs font-thin hidden sm:inline`}>{bytesToHuman(backup.bytes)}</span>
|
{backup.uuid}
|
||||||
}
|
</p>
|
||||||
</p>
|
</div>
|
||||||
<p css={tw`text-xs text-neutral-400 font-mono hidden md:block`}>
|
|
||||||
{backup.uuid}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div css={tw`ml-8 text-center`}>
|
<div css={tw`flex-1 md:flex-none md:w-48 mt-4 md:mt-0 md:ml-8 md:text-center`}>
|
||||||
<p
|
<p
|
||||||
title={format(backup.createdAt, 'ddd, MMMM do, yyyy HH:mm:ss')}
|
title={format(backup.createdAt, 'ddd, MMMM do, yyyy HH:mm:ss')}
|
||||||
css={tw`text-sm`}
|
css={tw`text-sm`}
|
||||||
|
@ -76,7 +78,7 @@ export default ({ backup, className }: Props) => {
|
||||||
<p css={tw`text-2xs text-neutral-500 uppercase mt-1`}>Created</p>
|
<p css={tw`text-2xs text-neutral-500 uppercase mt-1`}>Created</p>
|
||||||
</div>
|
</div>
|
||||||
<Can action={'backup.download'}>
|
<Can action={'backup.download'}>
|
||||||
<div css={tw`ml-6`} style={{ marginRight: '-0.5rem' }}>
|
<div css={tw`mt-4 md:mt-0 ml-6`} style={{ marginRight: '-0.5rem' }}>
|
||||||
{!backup.completedAt ?
|
{!backup.completedAt ?
|
||||||
<div css={tw`p-2 invisible`}>
|
<div css={tw`p-2 invisible`}>
|
||||||
<FontAwesomeIcon icon={faEllipsisH}/>
|
<FontAwesomeIcon icon={faEllipsisH}/>
|
||||||
|
|
|
@ -94,7 +94,7 @@ export default () => {
|
||||||
<ModalContent appear visible={visible} onDismissed={() => setVisible(false)}/>
|
<ModalContent appear visible={visible} onDismissed={() => setVisible(false)}/>
|
||||||
</Formik>
|
</Formik>
|
||||||
}
|
}
|
||||||
<Button onClick={() => setVisible(true)}>
|
<Button css={tw`w-full sm:w-auto`} onClick={() => setVisible(true)}>
|
||||||
Create backup
|
Create backup
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -38,13 +38,13 @@ const Clickable: React.FC<{ file: FileObject }> = memo(({ file, children }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(!canReadContents || (file.isFile && !file.isEditable())) ?
|
(!canReadContents || (file.isFile && !file.isEditable())) ?
|
||||||
<div css={tw`flex flex-1 text-neutral-300 no-underline p-3 cursor-default`}>
|
<div css={tw`flex flex-1 text-neutral-300 no-underline p-3 cursor-default overflow-hidden truncate`}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<NavLink
|
<NavLink
|
||||||
to={`${match.url}/${file.isFile ? 'edit/' : ''}#${cleanDirectoryPath(`${directory}/${file.name}`)}`}
|
to={`${match.url}/${file.isFile ? 'edit/' : ''}#${cleanDirectoryPath(`${directory}/${file.name}`)}`}
|
||||||
css={tw`flex flex-1 text-neutral-300 no-underline p-3`}
|
css={tw`flex flex-1 text-neutral-300 no-underline p-3 overflow-hidden truncate`}
|
||||||
onClick={onRowClick}
|
onClick={onRowClick}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -69,7 +69,7 @@ const FileObjectRow = ({ file }: { file: FileObject }) => (
|
||||||
<FontAwesomeIcon icon={faFolder}/>
|
<FontAwesomeIcon icon={faFolder}/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div css={tw`flex-1 break-all`}>
|
<div css={tw`flex-1 break-all truncate`}>
|
||||||
{file.name}
|
{file.name}
|
||||||
</div>
|
</div>
|
||||||
{file.isFile &&
|
{file.isFile &&
|
||||||
|
|
Loading…
Reference in a new issue