fix duplicate server add when SERVER_LOCK is configured (#1623)

This commit is contained in:
jeffvli
2026-02-13 19:42:36 -08:00
parent 2b4046a82e
commit dfbff64430
4 changed files with 58 additions and 18 deletions
+1
View File
@@ -227,6 +227,7 @@
"remotePortError": "an error occurred when trying to set the remote server port", "remotePortError": "an error occurred when trying to set the remote server port",
"remotePortWarning": "restart the server to apply the new port", "remotePortWarning": "restart the server to apply the new port",
"saveQueueFailed": "failed to save queue", "saveQueueFailed": "failed to save queue",
"serverLockSingleServer": "only one server is allowed when server is locked",
"serverNotSelectedError": "no server selected", "serverNotSelectedError": "no server selected",
"serverRequired": "server required", "serverRequired": "server required",
"sessionExpiredError": "your session has expired", "sessionExpiredError": "your session has expired",
@@ -17,7 +17,12 @@ import { IgnoreCorsSslSwitches } from '/@/renderer/features/servers/components/i
import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page';
import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useAuthStoreActions, useCurrentServer } from '/@/renderer/store'; import {
getServerById,
useAuthStoreActions,
useCurrentServer,
useServerList,
} from '/@/renderer/store';
import { Button } from '/@/shared/components/button/button'; import { Button } from '/@/shared/components/button/button';
import { Center } from '/@/shared/components/center/center'; import { Center } from '/@/shared/components/center/center';
import { Code } from '/@/shared/components/code/code'; import { Code } from '/@/shared/components/code/code';
@@ -46,11 +51,14 @@ const SERVER_NAMES: Record<ServerType, string> = {
[ServerType.SUBSONIC]: 'OpenSubsonic', [ServerType.SUBSONIC]: 'OpenSubsonic',
}; };
const normalizeUrl = (url: string) => url.replace(/\/$/, '');
const LoginRoute = () => { const LoginRoute = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { addServer, setCurrentServer } = useAuthStoreActions(); const { addServer, setCurrentServer, updateServer } = useAuthStoreActions();
const currentServer = useCurrentServer(); const currentServer = useCurrentServer();
const serverList = useServerList();
// Check if server lock is configured // Check if server lock is configured
const serverLock = isServerLock(); const serverLock = isServerLock();
@@ -141,23 +149,42 @@ const LoginRoute = () => {
}); });
} }
const normalizedUrl = normalizeUrl(serverUrl);
const existingServer =
serverLock &&
Object.values(serverList).find((s) => normalizeUrl(s.url) === normalizedUrl);
const serverItem: ServerListItemWithCredential = { const serverItem: ServerListItemWithCredential = {
credential: data.credential, credential: data.credential,
id: nanoid(), id: nanoid(),
isAdmin: data.isAdmin, isAdmin: data.isAdmin,
name: serverName, name: serverName,
type: serverType as ServerType, type: serverType as ServerType,
url: serverUrl.replace(/\/$/, ''), url: normalizedUrl,
userId: data.userId, userId: data.userId,
username: data.username, username: data.username,
}; };
if (existingServer) {
const updates: Partial<ServerListItemWithCredential> = {
credential: data.credential,
isAdmin: data.isAdmin,
userId: data.userId,
username: data.username,
};
if (data.ndCredential !== undefined) {
updates.ndCredential = data.ndCredential;
}
updateServer(existingServer.id, updates);
const updated = getServerById(existingServer.id);
if (updated) setCurrentServer(updated);
} else {
if (data.ndCredential !== undefined) { if (data.ndCredential !== undefined) {
serverItem.ndCredential = data.ndCredential; serverItem.ndCredential = data.ndCredential;
} }
addServer(serverItem); addServer(serverItem);
setCurrentServer(serverItem); setCurrentServer(serverItem);
}
toast.success({ toast.success({
message: t('form.addServer.success', { postProcess: 'sentenceCase' }), message: t('form.addServer.success', { postProcess: 'sentenceCase' }),
@@ -13,7 +13,7 @@ import JellyfinIcon from '/@/renderer/features/servers/assets/jellyfin.png';
import NavidromeIcon from '/@/renderer/features/servers/assets/navidrome.png'; import NavidromeIcon from '/@/renderer/features/servers/assets/navidrome.png';
import SubsonicIcon from '/@/renderer/features/servers/assets/opensubsonic.png'; import SubsonicIcon from '/@/renderer/features/servers/assets/opensubsonic.png';
import { IgnoreCorsSslSwitches } from '/@/renderer/features/servers/components/ignore-cors-ssl-switches'; import { IgnoreCorsSslSwitches } from '/@/renderer/features/servers/components/ignore-cors-ssl-switches';
import { useAuthStoreActions } from '/@/renderer/store'; import { useAuthStoreActions, useServerList } from '/@/renderer/store';
import { Checkbox } from '/@/shared/components/checkbox/checkbox'; import { Checkbox } from '/@/shared/components/checkbox/checkbox';
import { Divider } from '/@/shared/components/divider/divider'; import { Divider } from '/@/shared/components/divider/divider';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
@@ -98,6 +98,7 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
const focusTrapRef = useFocusTrap(true); const focusTrapRef = useFocusTrap(true);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const { addServer, setCurrentServer } = useAuthStoreActions(); const { addServer, setCurrentServer } = useAuthStoreActions();
const serverList = useServerList();
const { servers: discovered } = useAutodiscovery(); const { servers: discovered } = useAutodiscovery();
const serverLock = isServerLock(); const serverLock = isServerLock();
@@ -128,6 +129,13 @@ export const AddServerForm = ({ onCancel }: AddServerFormProps) => {
}; };
const handleSubmit = form.onSubmit(async (values) => { const handleSubmit = form.onSubmit(async (values) => {
if (serverLock && Object.keys(serverList).length >= 1) {
toast.error({
message: t('error.serverLockSingleServer', { postProcess: 'sentenceCase' }),
});
return;
}
const authFunction = api.controller.authenticate; const authFunction = api.controller.authenticate;
if (!authFunction) { if (!authFunction) {
@@ -2,6 +2,7 @@ import { openContextModal } from '@mantine/modals';
import isElectron from 'is-electron'; import isElectron from 'is-electron';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { isServerLock } from '/@/renderer/features/action-required/utils/window-properties';
import JellyfinLogo from '/@/renderer/features/servers/assets/jellyfin.png'; import JellyfinLogo from '/@/renderer/features/servers/assets/jellyfin.png';
import NavidromeLogo from '/@/renderer/features/servers/assets/navidrome.png'; import NavidromeLogo from '/@/renderer/features/servers/assets/navidrome.png';
import OpenSubsonicLogo from '/@/renderer/features/servers/assets/opensubsonic.png'; import OpenSubsonicLogo from '/@/renderer/features/servers/assets/opensubsonic.png';
@@ -23,6 +24,7 @@ export const ServerList = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const currentServer = useCurrentServer(); const currentServer = useCurrentServer();
const serverListQuery = useServerList(); const serverListQuery = useServerList();
const serverLock = isServerLock();
const handleAddServerModal = () => { const handleAddServerModal = () => {
openContextModal({ openContextModal({
@@ -70,6 +72,7 @@ export const ServerList = () => {
</Accordion.Item> </Accordion.Item>
); );
})} })}
{!serverLock && (
<Group grow pt="md"> <Group grow pt="md">
<Button <Button
autoFocus autoFocus
@@ -79,6 +82,7 @@ export const ServerList = () => {
{t('form.addServer.title', { postProcess: 'titleCase' })} {t('form.addServer.title', { postProcess: 'titleCase' })}
</Button> </Button>
</Group> </Group>
)}
</Accordion> </Accordion>
{isElectron() && ( {isElectron() && (
<> <>