mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 02:13:33 +00:00
Add autodiscovery for Jellyfin servers (#1146)
* Add autodiscovery for Jellyfin servers * Remove debugging aids you didn't see anything * Fix linter errors * Send a discovery packet to localhost too
This commit is contained in:
parent
bca4a14f2e
commit
e344adfeed
6 changed files with 237 additions and 91 deletions
|
|
@ -3,7 +3,7 @@ import { useFocusTrap } from '@mantine/hooks';
|
|||
import { closeAllModals } from '@mantine/modals';
|
||||
import isElectron from 'is-electron';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
|
|
@ -14,6 +14,7 @@ import { useAuthStoreActions } from '/@/renderer/store';
|
|||
import { Button } from '/@/shared/components/button/button';
|
||||
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Paper } from '/@/shared/components/paper/paper';
|
||||
import { PasswordInput } from '/@/shared/components/password-input/password-input';
|
||||
import { SegmentedControl } from '/@/shared/components/segmented-control/segmented-control';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
|
|
@ -21,14 +22,20 @@ import { TextInput } from '/@/shared/components/text-input/text-input';
|
|||
import { Text } from '/@/shared/components/text/text';
|
||||
import { toast } from '/@/shared/components/toast/toast';
|
||||
import { AuthenticationResponse } from '/@/shared/types/domain-types';
|
||||
import { ServerType, toServerType } from '/@/shared/types/types';
|
||||
import { DiscoveredServerItem, ServerType, toServerType } from '/@/shared/types/types';
|
||||
|
||||
const autodiscover = isElectron() ? window.api.autodiscover : null;
|
||||
const localSettings = isElectron() ? window.api.localSettings : null;
|
||||
|
||||
interface AddServerFormProps {
|
||||
onCancel: (() => void) | null;
|
||||
}
|
||||
|
||||
interface ServerDetails {
|
||||
icon: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
function ServerIconWithLabel({ icon, label }: { icon: string; label: string }) {
|
||||
return (
|
||||
<Stack align="center" justify="center">
|
||||
|
|
@ -38,26 +45,54 @@ function ServerIconWithLabel({ icon, label }: { icon: string; label: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
const SERVER_TYPES = [
|
||||
{
|
||||
label: <ServerIconWithLabel icon={JellyfinIcon} label="Jellyfin" />,
|
||||
value: ServerType.JELLYFIN,
|
||||
function useAutodiscovery() {
|
||||
const [isDone, setDone] = useState(false);
|
||||
const [servers, setServers] = useState<DiscoveredServerItem[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setServers([]);
|
||||
|
||||
autodiscover
|
||||
?.discover((newServer) => {
|
||||
setServers((tail) => [...tail, newServer]);
|
||||
})
|
||||
.then(() => {
|
||||
setDone(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { isDone, servers };
|
||||
}
|
||||
|
||||
const SERVER_TYPES: Record<ServerType, ServerDetails> = {
|
||||
[ServerType.JELLYFIN]: {
|
||||
icon: JellyfinIcon,
|
||||
name: 'Jellyfin',
|
||||
},
|
||||
{
|
||||
label: <ServerIconWithLabel icon={NavidromeIcon} label="Navidrome" />,
|
||||
value: ServerType.NAVIDROME,
|
||||
[ServerType.NAVIDROME]: {
|
||||
icon: NavidromeIcon,
|
||||
name: 'Navidrome',
|
||||
},
|
||||
{
|
||||
label: <ServerIconWithLabel icon={SubsonicIcon} label="OpenSubsonic" />,
|
||||
value: ServerType.SUBSONIC,
|
||||
[ServerType.SUBSONIC]: {
|
||||
icon: SubsonicIcon,
|
||||
name: 'OpenSubsonic',
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const ALL_SERVERS = Object.keys(SERVER_TYPES).map((serverType) => {
|
||||
const info = SERVER_TYPES[serverType];
|
||||
return {
|
||||
label: <ServerIconWithLabel icon={info.icon} label={info.name} />,
|
||||
value: serverType,
|
||||
};
|
||||
});
|
||||
|
||||
export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
||||
const { t } = useTranslation();
|
||||
const focusTrapRef = useFocusTrap(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { addServer, setCurrentServer } = useAuthStoreActions();
|
||||
const { servers: discovered } = useAutodiscovery();
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
|
|
@ -85,6 +120,10 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
|||
|
||||
const isSubmitDisabled = !form.values.name || !form.values.url || !form.values.username;
|
||||
|
||||
const fillServerDetails = (server: DiscoveredServerItem) => {
|
||||
form.setValues({ ...server });
|
||||
};
|
||||
|
||||
const handleSubmit = form.onSubmit(async (values) => {
|
||||
const authFunction = api.controller.authenticate;
|
||||
|
||||
|
|
@ -151,84 +190,104 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
|
|||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Stack m={5} ref={focusTrapRef}>
|
||||
<SegmentedControl
|
||||
data={SERVER_TYPES}
|
||||
disabled={Boolean(serverLock)}
|
||||
p="md"
|
||||
withItemsBorders={false}
|
||||
{...form.getInputProps('type')}
|
||||
/>
|
||||
<Group grow>
|
||||
<TextInput
|
||||
data-autofocus
|
||||
disabled={Boolean(serverLock)}
|
||||
label={t('form.addServer.input', {
|
||||
context: 'name',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<TextInput
|
||||
disabled={Boolean(serverLock)}
|
||||
label={t('form.addServer.input', {
|
||||
context: 'url',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('url')}
|
||||
/>
|
||||
</Group>
|
||||
<TextInput
|
||||
label={t('form.addServer.input', {
|
||||
context: 'username',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('username')}
|
||||
/>
|
||||
<PasswordInput
|
||||
label={t('form.addServer.input', {
|
||||
context: 'password',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('password')}
|
||||
/>
|
||||
{localSettings && form.values.type === ServerType.NAVIDROME && (
|
||||
<Checkbox
|
||||
label={t('form.addServer.input', {
|
||||
context: 'savePassword',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('savePassword', {
|
||||
type: 'checkbox',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{form.values.type === ServerType.SUBSONIC && (
|
||||
<Checkbox
|
||||
label={t('form.addServer.input', {
|
||||
context: 'legacyAuthentication',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('legacyAuth', { type: 'checkbox' })}
|
||||
/>
|
||||
)}
|
||||
<Group grow justify="flex-end">
|
||||
{onCancel && (
|
||||
<Button onClick={onCancel} variant="subtle">
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
disabled={isSubmitDisabled}
|
||||
loading={isLoading}
|
||||
type="submit"
|
||||
variant="filled"
|
||||
>
|
||||
{t('common.add', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
</Group>
|
||||
<>
|
||||
<Stack>
|
||||
{discovered.map((server) => (
|
||||
<Paper key={server.url} p="10px">
|
||||
<Group>
|
||||
<img height="32" src={SERVER_TYPES[server.type].icon} width="32" />
|
||||
<div
|
||||
onClick={() => fillServerDetails(server)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
<Text fw={700}>{server.name}</Text>
|
||||
<Text>
|
||||
{SERVER_TYPES[server.type].name} server at {server.url}
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
</form>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Stack m={5} ref={focusTrapRef}>
|
||||
<SegmentedControl
|
||||
data={ALL_SERVERS}
|
||||
disabled={Boolean(serverLock)}
|
||||
p="md"
|
||||
withItemsBorders={false}
|
||||
{...form.getInputProps('type')}
|
||||
/>
|
||||
<Group grow>
|
||||
<TextInput
|
||||
data-autofocus
|
||||
disabled={Boolean(serverLock)}
|
||||
label={t('form.addServer.input', {
|
||||
context: 'name',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<TextInput
|
||||
disabled={Boolean(serverLock)}
|
||||
label={t('form.addServer.input', {
|
||||
context: 'url',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('url')}
|
||||
/>
|
||||
</Group>
|
||||
<TextInput
|
||||
label={t('form.addServer.input', {
|
||||
context: 'username',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('username')}
|
||||
/>
|
||||
<PasswordInput
|
||||
label={t('form.addServer.input', {
|
||||
context: 'password',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('password')}
|
||||
/>
|
||||
{localSettings && form.values.type === ServerType.NAVIDROME && (
|
||||
<Checkbox
|
||||
label={t('form.addServer.input', {
|
||||
context: 'savePassword',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('savePassword', {
|
||||
type: 'checkbox',
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{form.values.type === ServerType.SUBSONIC && (
|
||||
<Checkbox
|
||||
label={t('form.addServer.input', {
|
||||
context: 'legacyAuthentication',
|
||||
postProcess: 'titleCase',
|
||||
})}
|
||||
{...form.getInputProps('legacyAuth', { type: 'checkbox' })}
|
||||
/>
|
||||
)}
|
||||
<Group grow justify="flex-end">
|
||||
{onCancel && (
|
||||
<Button onClick={onCancel} variant="subtle">
|
||||
{t('common.cancel', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
disabled={isSubmitDisabled}
|
||||
loading={isLoading}
|
||||
type="submit"
|
||||
variant="filled"
|
||||
>
|
||||
{t('common.add', { postProcess: 'titleCase' })}
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue