272 lines
8.0 KiB
TypeScript
272 lines
8.0 KiB
TypeScript
import {
|
|
Button,
|
|
type ComboboxItem,
|
|
type ComboboxLikeRenderOptionInput,
|
|
Grid,
|
|
GridCol,
|
|
Group,
|
|
LoadingOverlay,
|
|
Popover,
|
|
Select,
|
|
Stack,
|
|
TextInput,
|
|
} from '@mantine/core';
|
|
import { isNotEmpty, useForm } from '@mantine/form';
|
|
import { useDebouncedValue, useDisclosure } from '@mantine/hooks';
|
|
import { AddItemModal, NoItemsFound, SettingsHeader } from 'components';
|
|
import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks';
|
|
import { useEffect, useState } from 'react';
|
|
import { FaComputer, FaDesktop, FaTerminal } from 'react-icons/fa6';
|
|
import { buttonClasses, popoverClasses, selectClasses, textInputClasses } from 'styles';
|
|
import type { GuacamoleClient } from 'types';
|
|
import DebouncedGuacamoleClient from './DebouncedGuacamoleClient';
|
|
import GuacamoleClientSettingsItem from './GuacamoleClientSettingsItem';
|
|
|
|
export function GuacamoleClients() {
|
|
const defaultGuacamoleClient: GuacamoleClient = {
|
|
id: undefined,
|
|
clientName: '',
|
|
clientIdentifier: '',
|
|
enabled: 1,
|
|
type: 'SSH',
|
|
};
|
|
const form = useForm({
|
|
initialValues: defaultGuacamoleClient,
|
|
validate: {
|
|
clientName: isNotEmpty('Client Name is required'),
|
|
clientIdentifier: isNotEmpty('Client Identifier is required'),
|
|
},
|
|
});
|
|
const types: string[] = ['SSH', 'RDP', 'VNC'];
|
|
const [searchValue, setSearchValue] = useState<string | number>('');
|
|
const [debouncedSearch] = useDebouncedValue(searchValue, 500);
|
|
const endpoint = 'api/guacamoleClients';
|
|
const { data: guacamoleClientsData, isFetching: guacamoleClientsIsFetching } =
|
|
useQueryAPI({
|
|
endpoint: endpoint,
|
|
});
|
|
const guacamoleClients: GuacamoleClient[] =
|
|
guacamoleClientsData.filter(
|
|
(a: GuacamoleClient) =>
|
|
a.clientName.toLowerCase().includes((debouncedSearch as string).toLowerCase()) ||
|
|
a.clientIdentifier
|
|
.toLowerCase()
|
|
.includes((debouncedSearch as string).toLowerCase()),
|
|
) || [];
|
|
const { mutateAsync } = useMutationAPI();
|
|
const { isMobile, theme } = useCommonHooks();
|
|
const [modalOpened, { open: openModal, close: closeModal }] = useDisclosure(false);
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const handleClose = () => {
|
|
closeModal();
|
|
form.reset();
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (debouncedSearch === searchValue) {
|
|
setIsLoading(false);
|
|
}
|
|
}, [debouncedSearch]);
|
|
|
|
return (
|
|
<Stack>
|
|
{modalOpened && (
|
|
<AddItemModal
|
|
modalOpened={modalOpened}
|
|
handleClose={handleClose}
|
|
title={'Add a new Guacamole Client'}
|
|
body={
|
|
<form
|
|
onSubmit={form.onSubmit((values) => {
|
|
mutateAsync({
|
|
method: 'POST',
|
|
endpoint: endpoint,
|
|
body: { ...values, enabled: 1 },
|
|
});
|
|
handleClose();
|
|
})}
|
|
>
|
|
<Stack gap={0} w={'100%'} align='center'>
|
|
<Group w={'100%'} justify='center'>
|
|
<TextInput
|
|
key={form.key('clientName')}
|
|
{...form.getInputProps('clientName')}
|
|
label={'Client Name'}
|
|
placeholder='eg. Workstation'
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
withAsterisk
|
|
styles={
|
|
theme.textShadowsEnabled
|
|
? {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
<TextInput
|
|
key={form.key('clientIdentifier')}
|
|
{...form.getInputProps('clientIdentifier')}
|
|
label={'clientIdentifier'}
|
|
placeholder='eg. MzIAYwBteXNxbA'
|
|
className={textInputClasses.settingsItem}
|
|
classNames={textInputClasses}
|
|
withAsterisk
|
|
styles={
|
|
theme.textShadowsEnabled
|
|
? {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
</Group>
|
|
<Group w={'100%'} justify='center' pos={'relative'}>
|
|
<Select
|
|
key={form.key('type')}
|
|
{...form.getInputProps('type')}
|
|
label={'Type'}
|
|
className={selectClasses.settingsItem}
|
|
classNames={{
|
|
wrapper: selectClasses.menuSelectWrapper,
|
|
input: selectClasses.menuSelectInput,
|
|
label: selectClasses.menuSelectLabel,
|
|
options: selectClasses.menuSelectOptions,
|
|
option: selectClasses.menuSelectOption,
|
|
dropdown: selectClasses.dropdown,
|
|
}}
|
|
w={'45%'}
|
|
mt={'.75rem'}
|
|
limit={100}
|
|
placeholder='Client Type'
|
|
data={types.map((type) => {
|
|
return {
|
|
label: type,
|
|
value: type,
|
|
};
|
|
})}
|
|
comboboxProps={{
|
|
zIndex: 3000,
|
|
}}
|
|
styles={
|
|
theme.textShadowsEnabled
|
|
? {
|
|
label: { boxShadow: '2px 2px var(--mantine-color-border-0)' },
|
|
}
|
|
: undefined
|
|
}
|
|
renderOption={(item: ComboboxLikeRenderOptionInput<ComboboxItem>) => {
|
|
return (
|
|
<Group>
|
|
{item.option.value}
|
|
{item.option.value === 'SSH' && <FaTerminal />}
|
|
{item.option.value === 'RDP' && <FaComputer />}
|
|
{item.option.value === 'VNC' && <FaDesktop />}
|
|
</Group>
|
|
);
|
|
}}
|
|
searchable
|
|
maxDropdownHeight={175}
|
|
onDropdownOpen={() => setIsOpen(true)}
|
|
onDropdownClose={() => setIsOpen(false)}
|
|
withAsterisk
|
|
/>
|
|
</Group>
|
|
<Group mt={'1rem'}>
|
|
<Button
|
|
type='submit'
|
|
className={buttonClasses.saveButton}
|
|
classNames={buttonClasses}
|
|
>
|
|
Save
|
|
</Button>
|
|
<Button
|
|
className={buttonClasses.cancelButton}
|
|
classNames={buttonClasses}
|
|
onClick={handleClose}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</form>
|
|
}
|
|
/>
|
|
)}
|
|
<SettingsHeader
|
|
setDragging={undefined}
|
|
openModal={openModal}
|
|
title={'Guacamole Clients'}
|
|
tooltip={{
|
|
title: 'Guacamole clients can be configured here.',
|
|
highlightList: ['Clients', 'homepage', 'Guacamole'],
|
|
description:
|
|
'Clients can be connected to via the search bar on the homepage.\n\nThe name does not need to match a Guacamole host on the server, but is a convenient identifier inside your homepage. The Client Identifier must be the connection string from the guacamole server.',
|
|
}}
|
|
searchValue={searchValue}
|
|
setSearchValue={setSearchValue}
|
|
searchPlaceholder={'Search Clients...'}
|
|
isLoading={isLoading}
|
|
setIsLoading={setIsLoading}
|
|
/>
|
|
<Stack
|
|
mt={isMobile ? '10rem' : '6rem'}
|
|
mb={isMobile ? '6rem' : '1rem'}
|
|
w={'100%'}
|
|
align='center'
|
|
>
|
|
<LoadingOverlay
|
|
visible={guacamoleClientsIsFetching}
|
|
zIndex={180}
|
|
overlayProps={{ blur: 5 }}
|
|
loaderProps={{
|
|
color: 'var(--mantine-color-primary-0)',
|
|
type: 'bars',
|
|
size: 'xl',
|
|
}}
|
|
/>
|
|
<Grid columns={18} w={'75%'}>
|
|
{guacamoleClients.length > 0 ? (
|
|
guacamoleClients?.map((client: GuacamoleClient) => (
|
|
<GridCol w={'100%'} span={isMobile ? 18 : 6} key={client.id}>
|
|
<Popover
|
|
withArrow
|
|
withOverlay
|
|
trapFocus
|
|
closeOnClickOutside={!isOpen}
|
|
classNames={popoverClasses}
|
|
middlewares={{ shift: { padding: 20 } }}
|
|
position='bottom'
|
|
offset={isMobile ? -100 : 0}
|
|
>
|
|
<Popover.Target>
|
|
<Group justify='center'>
|
|
<GuacamoleClientSettingsItem client={client} />
|
|
</Group>
|
|
</Popover.Target>
|
|
<Popover.Dropdown w={isMobile ? '90%' : undefined}>
|
|
<DebouncedGuacamoleClient
|
|
guacamoleClient={client}
|
|
setIsOpen={setIsOpen}
|
|
/>
|
|
</Popover.Dropdown>
|
|
</Popover>
|
|
</GridCol>
|
|
))
|
|
) : (
|
|
<GridCol
|
|
w={'100%'}
|
|
span={18}
|
|
style={{ display: 'flex', justifyContent: 'center' }}
|
|
>
|
|
<NoItemsFound />
|
|
</GridCol>
|
|
)}
|
|
</Grid>
|
|
</Stack>
|
|
</Stack>
|
|
);
|
|
}
|