add initial files

This commit is contained in:
jeffvli
2022-07-25 19:40:16 -07:00
commit e8b612c974
283 changed files with 62820 additions and 0 deletions
@@ -0,0 +1,85 @@
import {
Button,
Checkbox,
Modal,
ModalProps,
PasswordInput,
SegmentedControl,
Stack,
TextInput,
} from '@mantine/core';
import { useForm } from '@mantine/form';
import { useTranslation } from 'react-i18next';
import { useCreateServer, validateServer } from '../queries/useCreateServer';
export const AddServerModal = ({ ...rest }: ModalProps) => {
const { t } = useTranslation();
const form = useForm({
initialValues: {
legacyAuth: false,
name: '',
password: '',
serverType: 'jellyfin',
url: 'http://',
username: '',
},
});
const createServerMutation = useCreateServer();
return (
<Modal centered title={t('server.add.title')} {...rest}>
<form
onSubmit={form.onSubmit(async (values) => {
const res = await validateServer(values);
if (res?.token) {
createServerMutation.mutateAsync({
...values,
remoteUserId: res.userId,
token: res.token,
});
}
})}
>
<Stack>
<SegmentedControl
data={[
{ label: 'Jellyfin', value: 'jellyfin' },
{ label: 'Subsonic', value: 'subsonic' },
]}
{...form.getInputProps('serverType')}
/>
<TextInput
required
label={t('server.name')}
{...form.getInputProps('name')}
/>
<TextInput
required
label={t('server.url')}
{...form.getInputProps('url')}
/>
<TextInput
required
label={t('server.username')}
{...form.getInputProps('username')}
/>
<PasswordInput
required
label={t('server.password')}
{...form.getInputProps('password')}
/>
{form.getInputProps('serverType').value === 'subsonic' && (
<Checkbox
label={t('server.legacyauth')}
{...form.getInputProps('legacyAuth', { type: 'checkbox' })}
/>
)}
<Button type="submit">{t('server.submit')}</Button>
</Stack>
</form>
</Modal>
);
};
@@ -0,0 +1,63 @@
import {
Button,
Checkbox,
ModalProps,
PasswordInput,
SegmentedControl,
Stack,
TextInput,
} from '@mantine/core';
import { useForm } from '@mantine/form';
import { useTranslation } from 'react-i18next';
import { ServerResponse } from 'renderer/api/types';
interface EditServerModalProps extends ModalProps {
server: ServerResponse | undefined;
}
export const EditServerModal = ({ server }: EditServerModalProps) => {
const { t } = useTranslation();
const form = useForm({
initialValues: {
legacyAuth: false,
name: server?.name,
password: '',
serverType: server?.serverType,
url: server?.url,
username: server?.username,
},
});
return (
<form onSubmit={form.onSubmit(async () => {})}>
<Stack>
<SegmentedControl
disabled
data={[
{ label: 'Jellyfin', value: 'jellyfin' },
{ label: 'Subsonic', value: 'subsonic' },
]}
{...form.getInputProps('serverType')}
/>
<TextInput label={t('server.name')} {...form.getInputProps('name')} />
<TextInput label={t('server.url')} {...form.getInputProps('url')} />
<TextInput
label={t('server.username')}
{...form.getInputProps('username')}
/>
<PasswordInput
label={t('server.password')}
{...form.getInputProps('password')}
/>
{form.getInputProps('serverType').value === 'subsonic' && (
<Checkbox
label={t('server.legacyauth')}
{...form.getInputProps('legacyAuth', { type: 'checkbox' })}
/>
)}
<Button type="submit">{t('server.submit')}</Button>
</Stack>
</form>
);
};
@@ -0,0 +1,8 @@
.item {
display: flex;
justify-content: space-between;
max-width: 50vw;
margin: 1rem;
padding: 1rem;
outline: 1px #fff solid;
}
@@ -0,0 +1,82 @@
import { useEffect, useState } from 'react';
import { Text } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { EditCircle } from 'tabler-icons-react';
import { ServerResponse } from 'renderer/api/types';
import { IconButton } from 'renderer/components';
import { useServers } from '../queries/useServers';
import { EditServerModal } from './EditServerModal';
import styles from './ServerList.module.scss';
export const ServerList = () => {
const { data: servers } = useServers();
const [editServerModal, editServerHandlers] = useDisclosure(false);
const [opened, setOpened] = useState(false);
const [selectedServer, setSelectedServer] = useState<
ServerResponse | undefined
>();
const handleClickServer = (server: ServerResponse) => {
setSelectedServer(server);
editServerHandlers.open();
};
const handleKeyDownServer = (
e: React.KeyboardEvent<HTMLDivElement>,
server: ServerResponse
) => {
if (e.key === ' ' || e.key === 'Enter') {
setSelectedServer(server);
editServerHandlers.open();
}
};
const handleCloseModal = () => {
setOpened(false);
editServerHandlers.close();
};
useEffect(() => {
if (editServerModal === true) {
setTimeout(() => setOpened(true));
} else {
setTimeout(() => setSelectedServer(undefined), 100);
}
}, [editServerModal]);
return (
<>
{servers &&
servers.data.map((server) => (
<>
<div
className={styles.item}
role="button"
tabIndex={0}
onClick={() => handleClickServer(server)}
onKeyDown={(e) => handleKeyDownServer(e, server)}
>
<div>
{server.name}
<Text>Hello</Text>
</div>
<IconButton
icon={<EditCircle />}
onClick={() => editServerHandlers.toggle()}
>
Edit
</IconButton>
</div>
{selectedServer && (
<EditServerModal
opened={opened}
server={selectedServer}
onClose={handleCloseModal}
/>
)}
</>
))}
</>
);
};
+5
View File
@@ -0,0 +1,5 @@
export * from './routes/ServersRoute';
export * from './queries/useCreateServer';
export * from './queries/useServers';
export * from './components/AddServerModal';
export * from './components/ServerList';
@@ -0,0 +1,70 @@
import axios from 'axios';
import md5 from 'md5';
import { useMutation } from 'react-query';
import { serversApi } from 'renderer/api/serversApi';
import { randomString } from 'renderer/utils';
export const validateServer = async (options: {
legacyAuth: boolean;
password: string;
serverType: string;
url: string;
username: string;
}) => {
const { serverType, url, username, password, legacyAuth } = options;
const cleanServerUrl = url.replace(/\/$/, '');
try {
if (serverType === 'subsonic') {
let testConnection;
let token;
if (legacyAuth) {
token = `u=${username}&p=${password}`;
testConnection = await axios.get(
`${cleanServerUrl}/rest/ping.view?v=1.13.0&c=sonixd&f=json&${token}`
);
} else {
const salt = randomString();
const hash = md5(password + salt);
token = `u=${username}&s=${salt}&t=${hash}`;
testConnection = await axios.get(
`${cleanServerUrl}/rest/ping.view?v=1.13.0&c=sonixd&f=json&${token}`
);
}
if (testConnection.data['subsonic-response'].status === 'failed') {
return {
error: `${testConnection.data['subsonic-response'].error.message}`,
};
}
return { token, userId: '' };
}
const { data } = await axios.post(
`${cleanServerUrl}/users/authenticatebyname`,
{ pw: password, username },
{
headers: {
'X-Emby-Authorization': `MediaBrowser Client="Sonixd", Device="PC", DeviceId="Sonixd", Version="1.0.0-alpha1"`,
},
}
);
return { token: data.AccessToken, userId: data.User.Id };
} catch (err) {
if (err instanceof Error) {
return { error: err.message };
}
}
return null;
};
export const useCreateServer = () => {
return useMutation({
mutationFn: serversApi.createServer,
onError: (e) => console.log(e),
onSuccess: (e) => console.log(e),
});
};
@@ -0,0 +1,68 @@
import md5 from 'md5';
import { useQuery } from 'react-query';
import { queryKeys } from 'renderer/api/queryKeys';
import { serversApi } from 'renderer/api/serversApi';
import { ServerFolderResponse } from 'renderer/api/types';
import { ServerFolderAuth } from 'types';
export const useServers = () => {
return useQuery({
onSuccess: (servers) => {
const { serverUrl } = JSON.parse(
localStorage.getItem('authentication') || '{}'
);
const storedServersKey = `servers_${md5(serverUrl)}`;
const serversFromLocalStorage = localStorage.getItem(storedServersKey);
// If a custom account/token is set for a server, use that instead of the default one
if (serversFromLocalStorage) {
const existingServers = JSON.parse(serversFromLocalStorage);
// The 'locked' property determines whether or not to skip updating the server auth
const skipped = existingServers.filter(
(server: ServerFolderAuth) => server.locked
);
const store = servers?.data?.flatMap((server) =>
server.serverFolder?.map((serverFolder: ServerFolderResponse) => {
if (skipped.includes(serverFolder.id)) {
return existingServers.find(
(s: ServerFolderAuth) => s.id === serverFolder.id
);
}
return {
id: serverFolder.id,
locked: false,
serverId: server.id,
token: server.token,
type: server.serverType,
url: server.url,
userId: server.remoteUserId,
username: server.username,
};
})
);
return localStorage.setItem(storedServersKey, JSON.stringify(store));
}
const store = servers?.data?.flatMap((server) =>
server.serverFolder?.map((serverFolder: ServerFolderResponse) => ({
id: serverFolder.id,
locked: false,
serverId: server.id,
token: server.token,
type: server.serverType,
url: server.url,
userId: server.remoteUserId,
username: server.username,
}))
);
return localStorage.setItem(storedServersKey, JSON.stringify(store));
},
queryFn: () => serversApi.getServers(),
queryKey: queryKeys.servers,
});
};
@@ -0,0 +1,11 @@
import { Title } from '@mantine/core';
import { ServerList } from '../components/ServerList';
export const ServersRoute = () => {
return (
<div>
<Title>Servers</Title>
<ServerList />
</div>
);
};