2020-04-04 05:39:53 +00:00
|
|
|
import React, { useEffect, useRef, useState } from 'react';
|
|
|
|
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
|
|
|
|
import { Field, Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
|
|
|
import { Actions, useStoreActions, useStoreState } from 'easy-peasy';
|
|
|
|
import { object, string } from 'yup';
|
2020-07-05 04:46:49 +00:00
|
|
|
import debounce from 'debounce';
|
2020-04-04 05:39:53 +00:00
|
|
|
import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper';
|
|
|
|
import InputSpinner from '@/components/elements/InputSpinner';
|
|
|
|
import getServers from '@/api/getServers';
|
|
|
|
import { Server } from '@/api/server/getServer';
|
|
|
|
import { ApplicationStore } from '@/state';
|
|
|
|
import { Link } from 'react-router-dom';
|
2020-07-03 21:19:05 +00:00
|
|
|
import styled from 'styled-components/macro';
|
|
|
|
import tw from 'twin.macro';
|
2020-07-04 22:19:46 +00:00
|
|
|
import Input from '@/components/elements/Input';
|
2021-12-04 18:35:55 +00:00
|
|
|
import { formatIp } from '@/helpers';
|
2020-04-04 05:39:53 +00:00
|
|
|
type Props = RequiredModalProps;
|
|
|
|
|
|
|
|
interface Values {
|
|
|
|
term: string;
|
|
|
|
}
|
|
|
|
|
2020-04-12 23:19:43 +00:00
|
|
|
const ServerResult = styled(Link)`
|
2020-07-05 20:56:04 +00:00
|
|
|
${tw`flex items-center bg-neutral-900 p-4 rounded border-l-4 border-neutral-900 no-underline transition-all duration-150`};
|
2020-04-12 23:19:43 +00:00
|
|
|
|
|
|
|
&:hover {
|
|
|
|
${tw`shadow border-cyan-500`};
|
|
|
|
}
|
|
|
|
|
|
|
|
&:not(:last-of-type) {
|
|
|
|
${tw`mb-2`};
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2020-04-04 05:39:53 +00:00
|
|
|
const SearchWatcher = () => {
|
|
|
|
const { values, submitForm } = useFormikContext<Values>();
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (values.term.length >= 3) {
|
|
|
|
submitForm();
|
|
|
|
}
|
|
|
|
}, [ values.term ]);
|
|
|
|
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
export default ({ ...props }: Props) => {
|
|
|
|
const ref = useRef<HTMLInputElement>(null);
|
|
|
|
const isAdmin = useStoreState(state => state.user.data!.rootAdmin);
|
2020-10-16 04:21:38 +00:00
|
|
|
const [ servers, setServers ] = useState<Server[]>([]);
|
|
|
|
const { clearAndAddHttpError, clearFlashes } = useStoreActions((actions: Actions<ApplicationStore>) => actions.flashes);
|
2020-04-04 05:39:53 +00:00
|
|
|
|
|
|
|
const search = debounce(({ term }: Values, { setSubmitting }: FormikHelpers<Values>) => {
|
|
|
|
clearFlashes('search');
|
2020-07-05 04:46:49 +00:00
|
|
|
|
2020-10-16 04:21:38 +00:00
|
|
|
// if (ref.current) ref.current.focus();
|
|
|
|
getServers({ query: term, type: isAdmin ? 'admin-all' : undefined })
|
2020-04-04 05:39:53 +00:00
|
|
|
.then(servers => setServers(servers.items.filter((_, index) => index < 5)))
|
|
|
|
.catch(error => {
|
|
|
|
console.error(error);
|
2020-10-16 04:21:38 +00:00
|
|
|
clearAndAddHttpError({ key: 'search', error });
|
2020-04-04 05:39:53 +00:00
|
|
|
})
|
2020-10-16 04:21:38 +00:00
|
|
|
.then(() => setSubmitting(false))
|
2020-10-16 03:09:13 +00:00
|
|
|
.then(() => ref.current?.focus());
|
2020-04-04 05:39:53 +00:00
|
|
|
}, 500);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (props.visible) {
|
2020-10-16 03:09:13 +00:00
|
|
|
if (ref.current) ref.current.focus();
|
2020-04-04 05:39:53 +00:00
|
|
|
}
|
|
|
|
}, [ props.visible ]);
|
|
|
|
|
2020-10-16 03:09:13 +00:00
|
|
|
// Formik does not support an innerRef on custom components.
|
2020-10-16 04:21:38 +00:00
|
|
|
const InputWithRef = (props: any) => <Input autoFocus {...props} ref={ref}/>;
|
2020-10-16 03:09:13 +00:00
|
|
|
|
2020-04-04 05:39:53 +00:00
|
|
|
return (
|
|
|
|
<Formik
|
|
|
|
onSubmit={search}
|
|
|
|
validationSchema={object().shape({
|
2020-10-16 03:09:13 +00:00
|
|
|
term: string().min(3, 'Please enter at least three characters to begin searching.'),
|
2020-04-04 05:39:53 +00:00
|
|
|
})}
|
|
|
|
initialValues={{ term: '' } as Values}
|
|
|
|
>
|
2020-10-16 04:21:38 +00:00
|
|
|
{({ isSubmitting }) => (
|
|
|
|
<Modal {...props}>
|
|
|
|
<Form>
|
|
|
|
<FormikFieldWrapper
|
|
|
|
name={'term'}
|
|
|
|
label={'Search term'}
|
|
|
|
description={'Enter a server name, uuid, or allocation to begin searching.'}
|
|
|
|
>
|
|
|
|
<SearchWatcher/>
|
|
|
|
<InputSpinner visible={isSubmitting}>
|
|
|
|
<Field as={InputWithRef} name={'term'}/>
|
|
|
|
</InputSpinner>
|
|
|
|
</FormikFieldWrapper>
|
|
|
|
</Form>
|
|
|
|
{servers.length > 0 &&
|
|
|
|
<div css={tw`mt-6`}>
|
|
|
|
{
|
|
|
|
servers.map(server => (
|
|
|
|
<ServerResult
|
|
|
|
key={server.uuid}
|
|
|
|
to={`/server/${server.id}`}
|
|
|
|
onClick={() => props.onDismissed()}
|
|
|
|
>
|
2020-10-16 04:23:31 +00:00
|
|
|
<div css={tw`flex-1 mr-4`}>
|
2020-10-16 04:21:38 +00:00
|
|
|
<p css={tw`text-sm`}>{server.name}</p>
|
|
|
|
<p css={tw`mt-1 text-xs text-neutral-400`}>
|
|
|
|
{
|
|
|
|
server.allocations.filter(alloc => alloc.isDefault).map(allocation => (
|
2021-12-04 18:35:55 +00:00
|
|
|
<span key={allocation.ip + allocation.port.toString()}>{allocation.alias || formatIp(allocation.ip)}:{allocation.port}</span>
|
2020-10-16 04:21:38 +00:00
|
|
|
))
|
|
|
|
}
|
|
|
|
</p>
|
|
|
|
</div>
|
2020-10-16 04:23:31 +00:00
|
|
|
<div css={tw`flex-none text-right`}>
|
|
|
|
<span css={tw`text-xs py-1 px-2 bg-cyan-800 text-cyan-100 rounded`}>
|
|
|
|
{server.node}
|
|
|
|
</span>
|
2020-10-16 04:21:38 +00:00
|
|
|
</div>
|
|
|
|
</ServerResult>
|
|
|
|
))
|
|
|
|
}
|
|
|
|
</div>
|
2020-04-04 05:39:53 +00:00
|
|
|
}
|
2020-10-16 04:21:38 +00:00
|
|
|
</Modal>
|
|
|
|
)}
|
2020-04-04 05:39:53 +00:00
|
|
|
</Formik>
|
|
|
|
);
|
|
|
|
};
|