mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 04:00:59 +01:00
web: add support for changing server urls
This commit is contained in:
committed by
Abdullah Atta
parent
22aae401ba
commit
5bbc0cd452
@@ -26,6 +26,7 @@ import { isFeatureSupported } from "../utils/feature-check";
|
||||
import { generatePassword } from "../utils/password-generator";
|
||||
import { deriveKey, useKeyStore } from "../interfaces/key-store";
|
||||
import { logManager } from "@notesnook/core/dist/logger";
|
||||
import Config from "../utils/config";
|
||||
|
||||
const db = database;
|
||||
async function initializeDatabase(persistence: DatabasePersistence) {
|
||||
@@ -45,7 +46,8 @@ async function initializeDatabase(persistence: DatabasePersistence) {
|
||||
AUTH_HOST: "https://auth.streetwriters.co",
|
||||
SSE_HOST: "https://events.streetwriters.co",
|
||||
ISSUES_HOST: "https://issues.streetwriters.co",
|
||||
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co"
|
||||
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
|
||||
...Config.get("serverUrls", {})
|
||||
});
|
||||
|
||||
const storage = new NNStorage(
|
||||
|
||||
@@ -55,6 +55,7 @@ function Field(props: FieldProps) {
|
||||
type,
|
||||
inputRef,
|
||||
validate,
|
||||
disabled,
|
||||
...inputProps
|
||||
} = props;
|
||||
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
||||
@@ -66,6 +67,8 @@ function Field(props: FieldProps) {
|
||||
sx={{
|
||||
m: "2px",
|
||||
mr: "2px",
|
||||
opacity: disabled ? 0.7 : 1,
|
||||
pointerEvents: disabled ? "none" : "all",
|
||||
...sx,
|
||||
flexDirection: "column"
|
||||
}}
|
||||
|
||||
@@ -214,7 +214,8 @@ import {
|
||||
mdiLink,
|
||||
mdiWindowClose,
|
||||
mdiFileMusicOutline,
|
||||
mdiBroom
|
||||
mdiBroom,
|
||||
mdiServerSecurity
|
||||
} from "@mdi/js";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Theme } from "@notesnook/theme";
|
||||
@@ -541,6 +542,7 @@ export const Documentation = createIcon(mdiFileDocumentOutline);
|
||||
export const Legal = createIcon(mdiGavel);
|
||||
export const Desktop = createIcon(mdiDesktopClassic);
|
||||
export const Notification = createIcon(mdiBellBadgeOutline);
|
||||
export const Servers = createIcon(mdiServerSecurity);
|
||||
export const Calendar = createIcon(mdiCalendarBlank);
|
||||
|
||||
export const WindowMinimize = createIcon("M4 20v-2h16v2H4Z");
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { Box, Text } from "@theme-ui/components";
|
||||
import Dialog from "../components/dialog";
|
||||
import { Loading } from "../components/icons";
|
||||
|
||||
type LoadingDialogProps<T> = {
|
||||
onClose: (result: T | boolean) => void;
|
||||
action: () => T | Promise<T>;
|
||||
title: string;
|
||||
description: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
function LoadingDialog<T>(props: LoadingDialogProps<T>) {
|
||||
const { onClose, action, description, message, title } = props;
|
||||
useEffect(() => {
|
||||
(async function () {
|
||||
try {
|
||||
onClose(await action());
|
||||
} catch (e) {
|
||||
onClose(false);
|
||||
throw e;
|
||||
}
|
||||
})();
|
||||
}, [onClose, action]);
|
||||
|
||||
return (
|
||||
<Dialog isOpen={true} title={title} description={description}>
|
||||
<Box>
|
||||
<Text as="span" variant="body">
|
||||
{message}
|
||||
</Text>
|
||||
<Loading sx={{ my: 2 }} color="accent" />
|
||||
</Box>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
export default LoadingDialog;
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import Field from "../../../components/field";
|
||||
import { useState } from "react";
|
||||
import { HostId, HostIds, useStore } from "../../../stores/setting-store";
|
||||
import { useStore as useUserStore } from "../../../stores/user-store";
|
||||
import { ErrorText } from "../../../components/error-text";
|
||||
import { TaskManager } from "../../../common/task-manager";
|
||||
|
||||
export const ServerIds = ["notesnook-sync", "auth", "sse"] as const;
|
||||
export type ServerId = (typeof ServerIds)[number];
|
||||
type Server = {
|
||||
id: ServerId;
|
||||
host: HostId;
|
||||
title: string;
|
||||
example: string;
|
||||
description: string;
|
||||
};
|
||||
type VersionResponse = {
|
||||
version: string;
|
||||
id: string;
|
||||
instance: string;
|
||||
};
|
||||
const SERVERS: Server[] = [
|
||||
{
|
||||
id: "notesnook-sync",
|
||||
host: "API_HOST",
|
||||
title: "Sync server",
|
||||
example: "http://localhost:4326",
|
||||
description: "Server used to sync your notes & other data between devices."
|
||||
},
|
||||
{
|
||||
id: "auth",
|
||||
host: "AUTH_HOST",
|
||||
title: "Auth server",
|
||||
example: "http://localhost:5326",
|
||||
description: "Server used for login/sign up and authentication."
|
||||
},
|
||||
{
|
||||
id: "sse",
|
||||
host: "SSE_HOST",
|
||||
title: "Events server",
|
||||
example: "http://localhost:7326",
|
||||
description: "Server used to receive important notifications & events."
|
||||
}
|
||||
];
|
||||
export function ServersConfiguration() {
|
||||
const [error, setError] = useState<string>();
|
||||
const [success, setSuccess] = useState<boolean>();
|
||||
const [urls, setUrls] = useState<Partial<Record<HostId, string>>>(
|
||||
useStore.getState().serverUrls
|
||||
);
|
||||
const isLoggedIn = useUserStore((store) => store.isLoggedIn);
|
||||
return (
|
||||
<>
|
||||
{isLoggedIn ? (
|
||||
<ErrorText
|
||||
error="You must log out in order to change/reset server URLs."
|
||||
sx={{ mb: 2, mt: 0 }}
|
||||
/>
|
||||
) : null}
|
||||
<Flex sx={{ flexDirection: "column" }}>
|
||||
{SERVERS.map((server) => (
|
||||
<Field
|
||||
disabled={isLoggedIn}
|
||||
key={server.id}
|
||||
label={`${server.title} URL`}
|
||||
helpText={server.description}
|
||||
placeholder={`e.g. ${server.example}`}
|
||||
validate={(text) => URL.canParse(text)}
|
||||
defaultValue={urls[server.host]}
|
||||
onChange={(e) =>
|
||||
setUrls((s) => {
|
||||
s[server.host] = e.target.value;
|
||||
return s;
|
||||
})
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<ErrorText error={error} />
|
||||
{success === true ? (
|
||||
<Text
|
||||
className="selectable"
|
||||
variant={"error"}
|
||||
ml={1}
|
||||
sx={{
|
||||
whiteSpace: "pre-wrap",
|
||||
bg: "shade",
|
||||
color: "accent",
|
||||
p: 1,
|
||||
mt: 2,
|
||||
borderRadius: "default"
|
||||
}}
|
||||
>
|
||||
Connected to all servers sucessfully.
|
||||
</Text>
|
||||
) : null}
|
||||
<Flex sx={{ mt: 1, justifyContent: "end", gap: 1 }}>
|
||||
<Button
|
||||
variant="accent"
|
||||
disabled={!success}
|
||||
onClick={async () => {
|
||||
if (!success || isLoggedIn) return;
|
||||
useStore.getState().setServerUrls(urls);
|
||||
await TaskManager.startTask({
|
||||
type: "modal",
|
||||
title: "App will reload in 5 seconds",
|
||||
subtitle:
|
||||
"Your changes have been saved and will be reflected after the app has refreshed.",
|
||||
action() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
resolve(undefined);
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Button
|
||||
disabled={isLoggedIn}
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
setError(undefined);
|
||||
try {
|
||||
for (const host of HostIds) {
|
||||
const url = urls[host];
|
||||
const server = SERVERS.find((s) => s.host === host)!;
|
||||
if (!url) throw new Error("All server urls are required.");
|
||||
const version = await fetch(`${url}/version`)
|
||||
.then((r) => r.json() as Promise<VersionResponse>)
|
||||
.catch(() => undefined);
|
||||
if (!version)
|
||||
throw new Error(`Could not connect to ${server.title}.`);
|
||||
if (version.id !== server.id)
|
||||
throw new Error(
|
||||
`The URL you have given (${url}) does not point to the ${server.title}.`
|
||||
);
|
||||
}
|
||||
setSuccess(true);
|
||||
} catch (e) {
|
||||
setError((e as Error).message);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Test connection
|
||||
</Button>
|
||||
<Button
|
||||
disabled={isLoggedIn}
|
||||
variant="errorSecondary"
|
||||
onClick={async () => {
|
||||
if (isLoggedIn) return;
|
||||
useStore.getState().setServerUrls();
|
||||
await TaskManager.startTask({
|
||||
type: "modal",
|
||||
title: "App will reload in 5 seconds",
|
||||
subtitle:
|
||||
"Your changes have been saved and will be reflected after the app has refreshed.",
|
||||
action() {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
resolve(undefined);
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
PasswordAndAuth,
|
||||
Privacy,
|
||||
Pro,
|
||||
Servers,
|
||||
ShieldLock,
|
||||
Sync
|
||||
} from "../../components/icons";
|
||||
@@ -74,6 +75,7 @@ import { SubscriptionSettings } from "./subscription-settings";
|
||||
import { ScopedThemeProvider } from "../../components/theme-provider";
|
||||
import { AppLockSettings } from "./app-lock-settings";
|
||||
import { BaseDialogProps, DialogManager } from "../../common/dialog-manager";
|
||||
import { ServersSettings } from "./servers-settings";
|
||||
|
||||
type SettingsDialogProps = BaseDialogProps<false>;
|
||||
|
||||
@@ -116,7 +118,8 @@ const sectionGroups: SectionGroup[] = [
|
||||
icon: Desktop,
|
||||
isHidden: () => !IS_DESKTOP_APP
|
||||
},
|
||||
{ key: "notifications", title: "Notifications", icon: Notification }
|
||||
{ key: "notifications", title: "Notifications", icon: Notification },
|
||||
{ key: "servers", title: "Servers", icon: Servers }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -164,7 +167,8 @@ const SettingsGroups = [
|
||||
...LegalSettings,
|
||||
...SupportSettings,
|
||||
...AboutSettings,
|
||||
...SubscriptionSettings
|
||||
...SubscriptionSettings,
|
||||
...ServersSettings
|
||||
];
|
||||
|
||||
// Thoughts:
|
||||
|
||||
41
apps/web/src/dialogs/settings/servers-settings.ts
Normal file
41
apps/web/src/dialogs/settings/servers-settings.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ServersConfiguration } from "./components/servers-configuration";
|
||||
import { SettingsGroup } from "./types";
|
||||
|
||||
export const ServersSettings: SettingsGroup[] = [
|
||||
{
|
||||
header: "Servers configuration",
|
||||
key: "servers",
|
||||
section: "servers",
|
||||
settings: [
|
||||
{
|
||||
key: "config",
|
||||
title: "",
|
||||
components: [
|
||||
{
|
||||
type: "custom",
|
||||
component: ServersConfiguration
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
@@ -28,6 +28,7 @@ export type SectionKeys =
|
||||
| "behaviour"
|
||||
| "desktop"
|
||||
| "notifications"
|
||||
| "servers"
|
||||
| "editor"
|
||||
| "backup-export"
|
||||
| "export"
|
||||
|
||||
@@ -29,6 +29,8 @@ import { setDocumentTitle } from "../utils/dom";
|
||||
import { TimeFormat } from "@notesnook/core/dist/utils/date";
|
||||
import { Profile, TrashCleanupInterval } from "@notesnook/core";
|
||||
|
||||
export const HostIds = ["API_HOST", "AUTH_HOST", "SSE_HOST"] as const;
|
||||
export type HostId = (typeof HostIds)[number];
|
||||
class SettingStore extends BaseStore<SettingStore> {
|
||||
encryptBackups = Config.get("encryptBackups", false);
|
||||
backupReminderOffset = Config.get("backupReminderOffset", 0);
|
||||
@@ -41,6 +43,7 @@ class SettingStore extends BaseStore<SettingStore> {
|
||||
markdownShortcuts = Config.get("markdownShortcuts", true);
|
||||
notificationsSettings = Config.get("notifications", { reminder: true });
|
||||
isFullOfflineMode = Config.get("fullOfflineMode", false);
|
||||
serverUrls: Partial<Record<HostId, string>> = Config.get("serverUrls", {});
|
||||
|
||||
zoomFactor = 1.0;
|
||||
privacyMode = false;
|
||||
@@ -217,6 +220,17 @@ class SettingStore extends BaseStore<SettingStore> {
|
||||
if (isFullOfflineMode) db.fs().cancel("offline-mode");
|
||||
else db.attachments.cacheAttachments();
|
||||
};
|
||||
|
||||
setServerUrls = (urls?: Partial<Record<HostId, string>>) => {
|
||||
if (!urls) {
|
||||
Config.set("serverUrls", {});
|
||||
this.set({ serverUrls: {} });
|
||||
return;
|
||||
}
|
||||
const serverUrls = this.get().serverUrls;
|
||||
this.set({ serverUrls: { ...serverUrls, ...urls } });
|
||||
Config.set("serverUrls", { ...serverUrls, ...urls });
|
||||
};
|
||||
}
|
||||
|
||||
const [useStore, store] = createStore<SettingStore>(
|
||||
|
||||
Reference in New Issue
Block a user