Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
pterodactyl
GitHub Repository: pterodactyl/panel
Path: blob/develop/resources/scripts/components/dashboard/search/SearchModal.tsx
7453 views
1
import { useEffect, useRef, useState } from 'react';
2
import Modal, { RequiredModalProps } from '@/components/elements/Modal';
3
import { Field, Form, Formik, FormikHelpers, useFormikContext } from 'formik';
4
import { Actions, useStoreActions, useStoreState } from 'easy-peasy';
5
import { object, string } from 'yup';
6
import debounce from 'debounce';
7
import FormikFieldWrapper from '@/components/elements/FormikFieldWrapper';
8
import InputSpinner from '@/components/elements/InputSpinner';
9
import getServers from '@/api/getServers';
10
import { Server } from '@/api/server/getServer';
11
import { ApplicationStore } from '@/state';
12
import { Link } from 'react-router-dom';
13
import styled from 'styled-components';
14
import tw from 'twin.macro';
15
import Input from '@/components/elements/Input';
16
import { ip } from '@/lib/formatters';
17
18
type Props = RequiredModalProps;
19
20
interface Values {
21
term: string;
22
}
23
24
const ServerResult = styled(Link)`
25
${tw`flex items-center bg-neutral-900 p-4 rounded border-l-4 border-neutral-900 no-underline transition-all duration-150`};
26
27
&:hover {
28
${tw`shadow border-cyan-500`};
29
}
30
31
&:not(:last-of-type) {
32
${tw`mb-2`};
33
}
34
`;
35
36
const SearchWatcher = () => {
37
const { values, submitForm } = useFormikContext<Values>();
38
39
useEffect(() => {
40
if (values.term.length >= 3) {
41
submitForm();
42
}
43
}, [values.term]);
44
45
return null;
46
};
47
48
export default ({ ...props }: Props) => {
49
const ref = useRef<HTMLInputElement>(null);
50
const isAdmin = useStoreState(state => state.user.data!.rootAdmin);
51
const [servers, setServers] = useState<Server[]>([]);
52
const { clearAndAddHttpError, clearFlashes } = useStoreActions(
53
(actions: Actions<ApplicationStore>) => actions.flashes,
54
);
55
56
const search = debounce(({ term }: Values, { setSubmitting }: FormikHelpers<Values>) => {
57
clearFlashes('search');
58
59
// if (ref.current) ref.current.focus();
60
getServers({ query: term, type: isAdmin ? 'admin-all' : undefined })
61
.then(servers => setServers(servers.items.filter((_, index) => index < 5)))
62
.catch(error => {
63
console.error(error);
64
clearAndAddHttpError({ key: 'search', error });
65
})
66
.then(() => setSubmitting(false))
67
.then(() => ref.current?.focus());
68
}, 500);
69
70
useEffect(() => {
71
if (props.visible) {
72
if (ref.current) ref.current.focus();
73
}
74
}, [props.visible]);
75
76
// Formik does not support an innerRef on custom components.
77
const InputWithRef = (props: any) => <Input autoFocus {...props} ref={ref} />;
78
79
return (
80
<Formik
81
onSubmit={search}
82
validationSchema={object().shape({
83
term: string().min(3, 'Please enter at least three characters to begin searching.'),
84
})}
85
initialValues={{ term: '' } as Values}
86
>
87
{({ isSubmitting }) => (
88
<Modal {...props}>
89
<Form>
90
<FormikFieldWrapper
91
name={'term'}
92
label={'Search term'}
93
description={'Enter a server name, uuid, or allocation to begin searching.'}
94
>
95
<SearchWatcher />
96
<InputSpinner visible={isSubmitting}>
97
<Field as={InputWithRef} name={'term'} />
98
</InputSpinner>
99
</FormikFieldWrapper>
100
</Form>
101
{servers.length > 0 && (
102
<div css={tw`mt-6`}>
103
{servers.map(server => (
104
<ServerResult
105
key={server.uuid}
106
to={`/server/${server.id}`}
107
onClick={() => props.onDismissed()}
108
>
109
<div css={tw`flex-1 mr-4`}>
110
<p css={tw`text-sm`}>{server.name}</p>
111
<p css={tw`mt-1 text-xs text-neutral-400`}>
112
{server.allocations
113
.filter(alloc => alloc.isDefault)
114
.map(allocation => (
115
<span key={allocation.ip + allocation.port.toString()}>
116
{allocation.alias || ip(allocation.ip)}:{allocation.port}
117
</span>
118
))}
119
</p>
120
</div>
121
<div css={tw`flex-none text-right`}>
122
<span css={tw`text-xs py-1 px-2 bg-cyan-800 text-cyan-100 rounded`}>
123
{server.node}
124
</span>
125
</div>
126
</ServerResult>
127
))}
128
</div>
129
)}
130
</Modal>
131
)}
132
</Formik>
133
);
134
};
135
136