Support filtering to own/all servers if user is an admin

This commit is contained in:
Dane Everitt 2020-04-25 17:52:32 -07:00
parent 67c6be9f6f
commit f45c03a449
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
4 changed files with 86 additions and 19 deletions

View file

@ -1,9 +1,16 @@
import { rawDataToServerObject, Server } from '@/api/server/getServer'; import { rawDataToServerObject, Server } from '@/api/server/getServer';
import http, { getPaginationSet, PaginatedResult } from '@/api/http'; import http, { getPaginationSet, PaginatedResult } from '@/api/http';
export default (query?: string): Promise<PaginatedResult<Server>> => { export default (query?: string, includeAdmin?: boolean): Promise<PaginatedResult<Server>> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
http.get(`/api/client`, { params: { include: [ 'allocation' ], query } }) http.get(`/api/client`, {
params: {
include: [ 'allocation' ],
// eslint-disable-next-line @typescript-eslint/camelcase
filter: includeAdmin ? 'all' : undefined,
query,
},
})
.then(({ data }) => resolve({ .then(({ data }) => resolve({
items: (data.data || []).map((datum: any) => rawDataToServerObject(datum.attributes)), items: (data.data || []).map((datum: any) => rawDataToServerObject(datum.attributes)),
pagination: getPaginationSet(data.meta.pagination), pagination: getPaginationSet(data.meta.pagination),

View file

@ -4,29 +4,62 @@ import getServers from '@/api/getServers';
import ServerRow from '@/components/dashboard/ServerRow'; import ServerRow from '@/components/dashboard/ServerRow';
import Spinner from '@/components/elements/Spinner'; import Spinner from '@/components/elements/Spinner';
import PageContentBlock from '@/components/elements/PageContentBlock'; import PageContentBlock from '@/components/elements/PageContentBlock';
import useFlash from '@/plugins/useFlash';
import { httpErrorToHuman } from '@/api/http';
import FlashMessageRender from '@/components/FlashMessageRender';
import { useStoreState } from 'easy-peasy';
import { usePersistedState } from '@/plugins/usePersistedState';
import Switch from '@/components/elements/Switch';
export default () => { export default () => {
const [ servers, setServers ] = useState<null | Server[]>(null); const { addError, clearFlashes } = useFlash();
const [ servers, setServers ] = useState<Server[]>([]);
const [ loading, setLoading ] = useState(true);
const { rootAdmin } = useStoreState(state => state.user.data!);
const [ showAdmin, setShowAdmin ] = usePersistedState('show_all_servers', false);
const loadServers = () => getServers().then(data => setServers(data.items)); const loadServers = () => {
clearFlashes();
setLoading(true);
getServers(undefined, showAdmin)
.then(data => setServers(data.items))
.catch(error => {
console.error(error);
addError({ message: httpErrorToHuman(error) });
})
.then(() => setLoading(false));
};
useEffect(() => { useEffect(() => {
loadServers(); loadServers();
}, []); }, [ showAdmin ]);
if (servers === null) {
return <Spinner size={'large'} centered={true}/>;
}
return ( return (
<PageContentBlock> <PageContentBlock>
{servers.length > 0 ? <FlashMessageRender className={'mb-4'}/>
{rootAdmin &&
<div className={'mb-2 flex justify-end items-center'}>
<p className={'uppercase text-xs text-neutral-400 mr-2'}>
{showAdmin ? 'Showing all servers' : 'Showing your servers'}
</p>
<Switch
name={'show_all_servers'}
defaultChecked={showAdmin}
onChange={() => setShowAdmin(s => !s)}
/>
</div>
}
{loading ?
<Spinner centered={true} size={'large'}/>
:
servers.length > 0 ?
servers.map(server => ( servers.map(server => (
<ServerRow key={server.uuid} server={server} className={'mt-2'}/> <ServerRow key={server.uuid} server={server} className={'mt-2'}/>
)) ))
: :
<p className={'text-center text-sm text-neutral-400'}> <p className={'text-center text-sm text-neutral-400'}>
It looks like you have no servers. There are no servers associated with your account.
</p> </p>
} }
</PageContentBlock> </PageContentBlock>

View file

@ -36,7 +36,7 @@ const ToggleContainer = styled.div`
export interface SwitchProps { export interface SwitchProps {
name: string; name: string;
label: string; label?: string;
description?: string; description?: string;
defaultChecked?: boolean; defaultChecked?: boolean;
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void; onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
@ -48,7 +48,7 @@ const Switch = ({ name, label, description, defaultChecked, onChange, children }
return ( return (
<div className={'flex items-center'}> <div className={'flex items-center'}>
<ToggleContainer className={'mr-4 flex-none'}> <ToggleContainer className={'flex-none'}>
{children {children
|| <input || <input
id={uuid} id={uuid}
@ -60,17 +60,21 @@ const Switch = ({ name, label, description, defaultChecked, onChange, children }
} }
<label htmlFor={uuid}/> <label htmlFor={uuid}/>
</ToggleContainer> </ToggleContainer>
<div className={'w-full'}> {(label || description) &&
<div className={'ml-4 w-full'}>
{label &&
<label <label
className={classNames('input-dark-label cursor-pointer', { 'mb-0': !!description })} className={classNames('input-dark-label cursor-pointer', { 'mb-0': !!description })}
htmlFor={uuid} htmlFor={uuid}
>{label}</label> >{label}</label>
}
{description && {description &&
<p className={'input-help'}> <p className={'input-help'}>
{description} {description}
</p> </p>
} }
</div> </div>
}
</div> </div>
); );
}; };

View file

@ -0,0 +1,23 @@
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
export function usePersistedState<S = undefined> (key: string, defaultValue: S): [S | undefined, Dispatch<SetStateAction<S | undefined>>] {
const [state, setState] = useState(
() => {
try {
const item = localStorage.getItem(key);
return JSON.parse(item || (String(defaultValue)));
} catch (e) {
console.warn('Failed to retrieve persisted value from store.', e);
return defaultValue;
}
}
);
useEffect(() => {
localStorage.setItem(key, JSON.stringify(state))
}, [key, state]);
return [ state, setState ];
}